在後台管理系統中,常需實現「功能開關」:管理員點擊開關按鈕,無刷新即時啟用/禁用某項功能(如維護模式、註冊開關、評論審核等)。本文以 360 安全衛士「木馬防火牆」開關 UI 為靈感,構建一個完整的 AJAX 實時切換方案。

CREATE TABLE `feature_switch` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`feature_key` VARCHAR(50) NOT NULL COMMENT '功能標識,如: maintenance_mode',
`feature_name` VARCHAR(100) NOT NULL COMMENT '功能顯示名稱',
`is_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '0關閉 1開啟',
`description` VARCHAR(255) DEFAULT NULL,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_feature_key` (`feature_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 初始化數據
INSERT INTO `feature_switch` (`feature_key`, `feature_name`, `is_enabled`, `description`) VALUES
('maintenance_mode', '網站維護模式', 0, '開啟後前台顯示維護頁面'),
('user_register', '用戶註冊功能', 1, '關閉後禁止新用戶註冊'),
('comment_audit', '評論人工審核', 1, '開啟後評論需後台審核才顯示');
<?php
require_once 'db.php'; // PDO 連接
header('Content-Type: application/json; charset=utf-8');
$action = $_POST['action'] ?? '';
$key = $_POST['key'] ?? '';
if ($action !== 'toggle' || !$key) {
echo json_encode(['code' => 400, 'msg' => '參數錯誤']);
exit;
}
try {
$pdo->beginTransaction();
// 行鎖防併發
$stmt = $pdo->prepare("SELECT is_enabled FROM feature_switch WHERE feature_key = ? FOR UPDATE");
$stmt->execute([$key]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
throw new Exception('功能不存在');
}
$newStatus = $row['is_enabled'] ? 0 : 1;
$stmt = $pdo->prepare("UPDATE feature_switch SET is_enabled = ? WHERE feature_key = ?");
$stmt->execute([$newStatus, $key]);
$pdo->commit();
// 這裡可加入緩存更新、日誌記錄、事件分發等
// $redis->set("switch:$key", $newStatus);
// Log::info("Switch toggled", ['key'=>$key, 'new'=>$newStatus, 'admin_id'=>getCurrentAdminId()]);
echo json_encode(['code' => 200, 'msg' => '操作成功', 'data' => ['enabled' => (bool)$newStatus]]);
} catch (Exception $e) {
$pdo->rollBack();
echo json_encode(['code' => 500, 'msg' => $e->getMessage()]);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>功能開關管理</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<style>
.switch { position: relative; display: inline-block; width: 60px; height: 30px; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 30px; }
.slider:before { position: absolute; content: ""; height: 22px; width: 22px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,.2); }
input:checked + .slider { background-color: #28a745; }
input:checked + .slider:before { transform: translateX(30px); }
.switch-label { margin-left: 12px; font-weight: 500; vertical-align: middle; }
.loading { display: none; width: 16px; height: 16px; border: 2px solid #28a745; border-radius: 50%; border-top-color: transparent; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body class="bg-light">
<div class="container py-5">
<h3 class="mb-4">系統功能開關</h3>
<div class="card">
<div class="card-body">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr><th>功能名稱</th><th>描述</th><th class="text-center" style="width:120px">狀態</th><th class="text-center" style="width:100px">操作</th></tr>
</thead>
<tbody id="switchList"></tbody>
</table>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script>
$(function() {
loadSwitches();
function loadSwitches() {
$.getJSON('switch_api.php?action=list', function(res) {
if (res.code !== 200) return;
let html = '';
res.data.forEach(item => {
html += `
<tr data-key="${item.feature_key}">
<td>${item.feature_name}</td>
<td class="text-muted">${item.description || '—'}</td>
<td class="text-center">
<label class="switch">
<input type="checkbox" class="toggle-switch" ${item.is_enabled ? 'checked' : ''} data-key="${item.feature_key}">
<span class="slider"></span>
</label>
<span class="switch-label">${item.is_enabled ? '已開啟' : '已關閉'}</span>
</td>
<td class="text-center">
<div class="loading" title="更新中..."></div>
</td>
</tr>`;
});
$('#switchList').html(html);
});
}
$('#switchList').on('change', '.toggle-switch', function() {
const $checkbox = $(this);
const key = $checkbox.data('key');
const $row = $checkbox.closest('tr');
const $label = $row.find('.switch-label');
const $loading = $row.find('.loading');
$checkbox.prop('disabled', true);
$loading.show();
$.post('switch_api.php', {action: 'toggle', key: key}, function(res) {
$loading.hide();
$checkbox.prop('disabled', false);
if (res.code === 200) {
$label.text(res.data.enabled ? '已開啟' : '已關閉');
// 可選:顯示 Toast 提示
} else {
// 失敗則回滾 UI
$checkbox.prop('checked', !$checkbox.prop('checked'));
alert('操作失敗:' + res.msg);
}
}, 'json').fail(function() {
$loading.hide();
$checkbox.prop('disabled', false);
$checkbox.prop('checked', !$checkbox.prop('checked'));
alert('網絡請求失敗,請重試');
});
});
});
</script>
</body>
</html>
在 switch_api.php 頂部添加:
if ($_GET['action'] ?? '' === 'list') {
$stmt = $pdo->query("SELECT feature_key, feature_name, is_enabled, description FROM feature_switch ORDER BY id");
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['code' => 200, 'data' => $data]);
exit;
}
通過 jQuery AJAX + PHP PDO + MySQL 行鎖 + Bootstrap Switch UI,我們實現了一個生產可用的功能開關系統。核心要點在於:併發安全(FOR UPDATE)、狀態原子翻轉、前端即時反饋、失敗回滾。該模式可直接應用於配置管理、功能開關、維護模式等場景,極大提升運營效率與用戶體驗。