首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP + AJAX 二級聯動下拉選單:Prototype.js 實現無刷新動態載入

     編輯:PHP 相關知識

技術棧

  • 後端:PHP + MySQL
  • 前端:Prototype.js 1.6 (經典 AJAX 框架,現代項目建議改用 Fetch/Axios)
  • 快取:檔案快取機制,減少資料庫查詢

資料庫結構

CREATE TABLE `categories` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `name` VARCHAR(50) NOT NULL,
  `parent_id` INT UNSIGNED DEFAULT 0,
  INDEX `idx_parent` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 範例資料
INSERT INTO categories (name, parent_id) VALUES
('電子產品', 0), ('服飾鞋包', 0), ('家居生活', 0),
('手機', 1), ('筆電', 1), ('配件', 1),
('男裝', 2), ('女裝', 2), ('鞋類', 2);

後端實現

1. 共用配置 (include/config_base.php)

<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'shop');
define('DB_USER', 'root');
define('DB_PASS', 'password');
define('CACHE_DIR', __DIR__ . '/../cache/');

2. 快取工具 (include/inc_cache.php)

<?php
function cache_write(string $file, string $content): int {
  $path = CACHE_DIR . $file;
  $dir = dirname($path);
  if (!is_dir($dir)) mkdir($dir, 0755, true);
  return file_put_contents($path, $content, LOCK_EX);
}

function cache_read(string $file): string {
  $path = CACHE_DIR . $file;
  return file_exists($path) ? file_get_contents($path) : '';
}

function cache_exists(string $file): bool {
  return file_exists(CACHE_DIR . $file);
}

3. 分類模型 (include/inc_type.php)

<?php
class Type {
  private $pdo;
  
  public function __construct() {
    $this->pdo = new PDO(
      "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
      DB_USER, DB_PASS,
      [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    );
  }
  
  // 取得大分類選項
  public function getBigOptions(): string {
    $stmt = $this->pdo->query("SELECT id, name FROM categories WHERE parent_id = 0 ORDER BY id");
    $html = '';
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
      $html .= "";
    }
    return $html;
  }
  
  // 取得子分類選項
  public function getSmallOptions(int $parentId): string {
    $stmt = $this->pdo->prepare("SELECT id, name FROM categories WHERE parent_id = ? ORDER BY id");
    $stmt->execute([$parentId]);
    $html = "";
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
      $html .= "";
    }
    return $html;
  }
  
  public function close() { $this->pdo = null; }
}

4. 大分類 AJAX 端點 (GetBigType.php)

<?php
header("Content-Type: text/html; charset=utf-8");
require_once __DIR__ . '/include/config_base.php';
require_once __DIR__ . '/include/inc_type.php';
require_once __DIR__ . '/include/inc_cache.php';

$cacheFile = 'bigtype.cache.txt';

if (!cache_exists($cacheFile)) {
  $type = new Type();
  $options = $type->getBigOptions();
  cache_write($cacheFile, $options);
  $type->close();
} else {
  $options = cache_read($cacheFile);
}

echo "";

5. 子分類 AJAX 端點 (GetSmallType.php)

<?php
header("Content-Type: text/html; charset=utf-8");
require_once __DIR__ . '/include/config_base.php';
require_once __DIR__ . '/include/inc_type.php';
require_once __DIR__ . '/include/inc_cache.php';

$fatherId = (int)($_GET['fatherid'] ?? 0);
if ($fatherId <= 0) exit;

$cacheFile = "sun_{$fatherId}_cache.txt";

if (!cache_exists($cacheFile)) {
  $type = new Type();
  $options = $type->getSmallOptions($fatherId);
  cache_write($cacheFile, $options);
  $type->close();
} else {
  $options = cache_read($cacheFile);
}

echo "";

前端頁面

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>二級聯動選單</title>
<script src="https://cdn.jsdelivr.net/npm/prototypejs@1.7.3/dist/prototype.min.js"></script>
<style>
  select { margin: 5px; padding: 5px; }
  #loading { color: #666; font-size: 12px; }
</style></head><body>

<h3>商品分類篩選</h3>
<div id="BigType"></div>
<div id="SmallType"></div>
<div id="loading" style="display:none">載入中...</div>

<script>
// 載入大分類
function GetBigType() {
  $('loading').show();
  new Ajax.Updater('BigType', 'GetBigType.php', {
    method: 'get',
    onComplete: function() { $('loading').hide(); }
  });
}

// 載入子分類
function GetSmallType() {
  const fatherId = $F('fatherid');
  if (!fatherId) { $('SmallType').update(''); return; }
  
  $('loading').show();
  new Ajax.Updater('SmallType', 'GetSmallType.php', {
    method: 'get',
    parameters: { fatherid: fatherId },
    onComplete: function() { $('loading').hide(); }
  });
}

// 初始化
document.observe('dom:loaded', GetBigType);
</script>
</body></html>

Prototype.js 關鍵 API 解析

方法用法說明
$('id')$('elementId')document.getElementById 簡寫,並擴充 Element 方法
$F('id')$F('elementId')取得表單元素值 (Form.Element.getValue)
Ajax.Updaternew Ajax.Updater(container, url, options)自動將回應內容注入 container (innerHTML)
Ajax.Requestnew Ajax.Request(url, options)底層請求,需手動處理回應
Element.update$('id').update(html)設定 innerHTML
document.observedocument.observe('dom:loaded', fn)DOMContentLoaded 事件

現代化重寫:Fetch + JSON (推薦新專案)

後端改為 JSON (GetSmallTypeJson.php)

<?php
header("Content-Type: application/json; charset=utf-8");
require_once __DIR__ . '/include/config_base.php';

$fatherId = (int)($_GET['fatherid'] ?? 0);
if ($fatherId <= 0) exit(json_encode([]));

$pdo = new PDO(
  "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
  DB_USER, DB_PASS,
  [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

$stmt = $pdo->prepare("SELECT id, name FROM categories WHERE parent_id = ? ORDER BY id");
$stmt->execute([$fatherId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 加上預設選項
array_unshift($rows, ['id' => 0, 'name' => '-- 不限子分類 --']);

echo json_encode($rows);

前端原生 JS (無依賴)

<script>
async function loadBigType() {
  const resp = await fetch('GetBigTypeJson.php');
  const data = await resp.json();
  const sel = document.getElementById('fatherid');
  sel.innerHTML = data.map(d => ``).join('');
}

async function loadSmallType() {
  const fatherId = document.getElementById('fatherid').value;
  const resp = await fetch(`GetSmallTypeJson.php?fatherid=${fatherId}`);
  const data = await resp.json();
  const sel = document.getElementById('sunid');
  sel.innerHTML = data.map(d => ``).join('');
}

// 事件綁定
document.addEventListener('DOMContentLoaded', loadBigType);
document.getElementById('fatherid').addEventListener('change', loadSmallType);
</script>

快取策略對比

策略優點缺點適用
檔案快取 (原教程)簡單、無額外依賴高併發下鎖競爭、分散式部署需共享儲存中小流量、單機
Redis 快取高效能、支援集群、TTL 自動過期需額外基建高併發、分散式
APCu (PHP 共享記憶體)極快、無網路開銷單機、重啟失效單機高頻讀取
CDN/Edge 快取就近訪問、減輕源站動態參數難快取靜態/低變化選單

安全性補強

  • 輸入驗證:(int)$_GET['fatherid'] 強制轉型,防 SQL 注入
  • 輸出編碼:htmlspecialchars() 或 JSON 自動處理
  • CSRF 保護:狀態改變操作需 Token,純查詢可豁免
  • 頻率限制:單 IP 限制請求頻率,防濫用
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved