253 行
11 KiB
PHP
253 行
11 KiB
PHP
<?php
|
|
require __DIR__ . '/includes/init.php';
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('X-Content-Type-Options: nosniff');
|
|
function json_ok($data = null, $msg = '') {
|
|
echo json_encode(['ok' => true, 'msg' => $msg, 'data' => $data], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
function json_err($code = 400, $msg = '操作失败') {
|
|
http_response_code($code);
|
|
echo json_encode(['ok' => false, 'msg' => $msg], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
function requireCsrf() {
|
|
$token = trim($_SERVER['HTTP_X_CSRF_TOKEN'] ?? $_POST['csrf_token'] ?? '');
|
|
if (!$token || !verifyCsrfToken($token)) {
|
|
json_err(403, 'CSRF 验证失败,请刷新页面重试');
|
|
}
|
|
}
|
|
$action = trim($_GET['action'] ?? $_POST['action'] ?? '');
|
|
$ROUTES = [
|
|
'cart_get' => true,
|
|
'cart_add' => true,
|
|
'cart_update' => true,
|
|
'cart_remove' => true,
|
|
'cart_clear' => true,
|
|
'sign_in' => true,
|
|
'ticket_submit'=> true,
|
|
'search' => false,
|
|
'products' => false,
|
|
'product_info' => false,
|
|
'announcements'=> false,
|
|
];
|
|
if (!isset($ROUTES[$action])) {
|
|
json_err(404, '未知操作: ' . h($action));
|
|
}
|
|
$needAuth = $ROUTES[$action];
|
|
if ($needAuth && empty($_SESSION['user_id'])) {
|
|
json_err(401, '请先登录');
|
|
}
|
|
$user = null;
|
|
if ($needAuth) {
|
|
$user = currentUser();
|
|
if (!$user) { json_err(401, '登录已过期'); }
|
|
}
|
|
$WRITE_ACTIONS = ['cart_add', 'cart_update', 'cart_remove', 'cart_clear', 'sign_in', 'ticket_submit'];
|
|
if (in_array($action, $WRITE_ACTIONS)) {
|
|
requireCsrf();
|
|
}
|
|
switch ($action) {
|
|
case 'cart_get':
|
|
$items = [];
|
|
$total = 0;
|
|
if (!empty($_SESSION['cart']) && is_array($_SESSION['cart'])) {
|
|
$ids = array_keys($_SESSION['cart']);
|
|
if (!empty($ids)) {
|
|
$inPlaceholders = str_repeat('?', count($ids));
|
|
$stmt = db()->prepare(
|
|
'SELECT id,name,image,icon,category,points_price,stock,status FROM ' . tn('products') .
|
|
' WHERE id IN (' . $inPlaceholders . ') AND status=1'
|
|
);
|
|
$stmt->execute($ids);
|
|
while ($p = $stmt->fetch()) {
|
|
$qty = (int)($_SESSION['cart'][$p['id']] ?? 0);
|
|
if ($qty <= 0) continue;
|
|
$price = !empty($p['points_price']) ? (int)$p['points_price'] : 0;
|
|
$items[] = [
|
|
'id' => (int)$p['id'],
|
|
'name' => $p['name'],
|
|
'image' => $p['image'] ?: '',
|
|
'icon' => $p['icon'] ?: '',
|
|
'category' => $p['category'],
|
|
'qty' => $qty,
|
|
'points_price' => $price,
|
|
'stock' => (int)$p['stock'],
|
|
'subtotal' => $price * $qty,
|
|
];
|
|
$total += $price * $qty;
|
|
}
|
|
}
|
|
}
|
|
json_ok(['items' => $items, 'count' => count($items), 'total_points' => $total]);
|
|
case 'cart_add':
|
|
$pid = (int)($_POST['product_id'] ?? 0);
|
|
$qty = max(1, min(99, (int)($_POST['qty'] ?? 1)));
|
|
if ($pid <= 0) { json_err(400, '商品 ID 无效'); }
|
|
$stmt = db()->prepare('SELECT id,status,stock FROM ' . tn('products') . ' WHERE id=?');
|
|
$stmt->execute([$pid]);
|
|
$p = $stmt->fetch();
|
|
if (!$p || (int)$p['status'] !== 1) { json_err(400, '商品不存在或已下架'); }
|
|
if (!isset($_SESSION['cart'])) { $_SESSION['cart'] = []; }
|
|
$current = (int)($_SESSION['cart'][$pid] ?? 0);
|
|
$newQty = $current + $qty;
|
|
if ((int)$p['stock'] >= 0 && $newQty > (int)$p['stock']) {
|
|
json_err(400, '库存不足(剩余 ' . (int)$p['stock'] . ' 件)');
|
|
}
|
|
$_SESSION['cart'][$pid] = $newQty;
|
|
$count = array_sum(array_map('intval', $_SESSION['cart']));
|
|
json_ok(['cart_count' => $count, 'item_qty' => $newQty], '已加入购物车');
|
|
case 'cart_update':
|
|
$pid = (int)($_POST['product_id'] ?? 0);
|
|
$qty = max(1, min(99, (int)($_POST['qty'] ?? 1)));
|
|
if ($pid <= 0) { json_err(400, '商品 ID 无效'); }
|
|
if (!isset($_SESSION['cart'][$pid])) { json_err(400, '购物车中没有该商品'); }
|
|
$stmt = db()->prepare('SELECT stock FROM ' . tn('products') . ' WHERE id=? AND status=1');
|
|
$stmt->execute([$pid]);
|
|
$p = $stmt->fetch();
|
|
if (!$p) { json_err(400, '商品不存在或已下架'); }
|
|
if ((int)$p['stock'] >= 0 && $qty > (int)$p['stock']) {
|
|
json_err(400, '库存不足');
|
|
}
|
|
$_SESSION['cart'][$pid] = $qty;
|
|
$count = array_sum(array_map('intval', $_SESSION['cart']));
|
|
json_ok(['cart_count' => $count], '数量已更新');
|
|
case 'cart_remove':
|
|
$pid = (int)($_POST['product_id'] ?? 0);
|
|
if ($pid <= 0) { json_err(400, '商品 ID 无效'); }
|
|
unset($_SESSION['cart'][$pid]);
|
|
$count = isset($_SESSION['cart']) ? array_sum(array_map('intval', $_SESSION['cart'])) : 0;
|
|
json_ok(['cart_count' => $count], '已移除');
|
|
case 'cart_clear':
|
|
$_SESSION['cart'] = [];
|
|
json_ok([], '购物车已清空');
|
|
case 'sign_in':
|
|
$enabled = getSetting('points_enabled', '1') === '1';
|
|
if (!$enabled) { json_err(400, '积分系统未启用'); }
|
|
$today = date('Y-m-d');
|
|
$stmt = db()->prepare('SELECT id FROM ' . tn('sign_logs') . ' WHERE user_id=? AND sign_date=?');
|
|
$stmt->execute([$user['id'], $today]);
|
|
if ($stmt->fetch()) { json_err(400, '今天已经签到了'); }
|
|
$points = max(0, (int)getSetting('points_sign', 5));
|
|
db()->beginTransaction();
|
|
try {
|
|
db()->prepare('INSERT INTO ' . tn('sign_logs') . ' (user_id,sign_date,points) VALUES (?,?,?)')
|
|
->execute([$user['id'], $today, $points]);
|
|
db()->prepare('UPDATE ' . tn('users') . ' SET points=points+? WHERE id=?')
|
|
->execute([$points, $user['id']]);
|
|
db()->commit();
|
|
} catch (Exception $e) {
|
|
db()->rollBack();
|
|
json_err(500, '签到失败: 数据库错误');
|
|
}
|
|
$newTotal = getUserPoints($user['id']);
|
|
json_ok(['points_gained' => $points, 'points_total' => $newTotal], "签到成功!+{$points} 积分");
|
|
case 'ticket_submit':
|
|
$deptId = (int)($_POST['dept_id'] ?? 0);
|
|
$title = trim($_POST['title'] ?? '');
|
|
$body = trim($_POST['body'] ?? '');
|
|
$priority = in_array($_POST['priority'] ?? '', ['low','normal','high']) ? $_POST['priority'] : 'normal';
|
|
if ($title === '' || strlen($title) < 3) { json_err(400, '标题至少 3 个字'); }
|
|
if ($body === '' || strlen($body) < 5) { json_err(400, '描述至少 5 个字'); }
|
|
if ($deptId > 0) {
|
|
$ds = db()->prepare('SELECT id FROM ' . tn('departments') . ' WHERE id=?');
|
|
$ds->execute([$deptId]);
|
|
if (!$ds->fetch()) { $deptId = 0; }
|
|
}
|
|
$orderId = !empty($_POST['order_id']) ? (int)$_POST['order_id'] : null;
|
|
db()->prepare(
|
|
'INSERT INTO ' . tn('tickets') . ' (user_id,dept_id,title,body,priority,order_id,status,created_at) VALUES (?,?,?,?,?,?,\'open\',NOW())'
|
|
)->execute([$user['id'], $deptId, $title, $body, $priority, $orderId]);
|
|
$ticketId = (int)db()->lastInsertId();
|
|
notifyAdmins('新工单
|
|
json_ok(['ticket_id' => $ticketId], '工单提交成功!我们会尽快处理。');
|
|
case 'search':
|
|
$q = trim($_GET['q'] ?? '');
|
|
if ($q === '') { json_err(400, '请输入搜索关键词'); }
|
|
$where = ['status = 1', 'name LIKE ?'];
|
|
$params = ['%' . escLike($q) . '%'];
|
|
$sql = 'SELECT id,name,category,image,icon,points_price,stock,description FROM ' . tn('products') .
|
|
' WHERE ' . implode(' AND ', $where) . ' ORDER BY created_at DESC LIMIT 50';
|
|
$stmt = db()->prepare($sql);
|
|
$stmt->execute($params);
|
|
$products = [];
|
|
while ($r = $stmt->fetch()) {
|
|
$products[] = [
|
|
'id' => (int)$r['id'],
|
|
'name' => $r['name'],
|
|
'category' => $r['category'],
|
|
'image' => $r['image'] ?: '',
|
|
'icon' => $r['icon'] ?: '',
|
|
'points_price' => !empty($r['points_price']) ? (int)$r['points_price'] : null,
|
|
'stock' => (int)$r['stock'],
|
|
'description' => mb_substr($r['description'] ?? '', 0, 100),
|
|
];
|
|
}
|
|
json_ok(['products' => $products, 'query' => $q, 'count' => count($products)]);
|
|
case 'products':
|
|
$cat = trim($_GET['cat'] ?? '');
|
|
$where = ['status = 1'];
|
|
$params = [];
|
|
if ($cat !== '') {
|
|
$where[] = 'category = ?';
|
|
$params[] = $cat;
|
|
}
|
|
$sql = 'SELECT id,name,category,image,icon,points_price,stock FROM ' . tn('products') .
|
|
' WHERE ' . implode(' AND ', $where) . ' ORDER BY created_at DESC LIMIT 100';
|
|
$stmt = db()->prepare($sql);
|
|
$stmt->execute($params);
|
|
$products = [];
|
|
while ($r = $stmt->fetch()) {
|
|
$products[] = [
|
|
'id' => (int)$r['id'],
|
|
'name' => $r['name'],
|
|
'category' => $r['category'],
|
|
'image' => $r['image'] ?: '',
|
|
'icon' => $r['icon'] ?: '',
|
|
'points_price' => !empty($r['points_price']) ? (int)$r['points_price'] : null,
|
|
'stock' => (int)$r['stock'],
|
|
];
|
|
}
|
|
json_ok(['products' => $products, 'category' => $cat, 'count' => count($products)]);
|
|
case 'product_info':
|
|
$pid = (int)($_GET['id'] ?? 0);
|
|
if ($pid <= 0) { json_err(400, '商品 ID 无效'); }
|
|
$stmt = db()->prepare(
|
|
'SELECT id,name,category,image,icon,points_price,cash_price,stock,period,description,created_at FROM ' . tn('products') . ' WHERE id=? AND status=1'
|
|
);
|
|
$stmt->execute([$pid]);
|
|
$p = $stmt->fetch();
|
|
if (!$p) { json_err(404, '商品不存在'); }
|
|
json_ok([
|
|
'id' => (int)$p['id'],
|
|
'name' => $p['name'],
|
|
'category' => $p['category'],
|
|
'image' => $p['image'] ?: '',
|
|
'icon' => $p['icon'] ?: '',
|
|
'points_price' => !empty($p['points_price']) ? (int)$p['points_price'] : null,
|
|
'cash_price' => !empty($p['cash_price']) ? floatval($p['cash_price']) : null,
|
|
'stock' => (int)$p['stock'],
|
|
'period' => $p['period'] ?: '',
|
|
'description' => $p['description'] ?: '',
|
|
]);
|
|
case 'announcements':
|
|
$limit = min(20, max(1, (int)($_GET['limit'] ?? 10)));
|
|
$stmt = db()->query(
|
|
'SELECT id,title,pinned,content,created_at FROM ' . tn('announcements') .
|
|
' WHERE status=1 ORDER BY pinned DESC, created_at DESC LIMIT ' . $limit
|
|
);
|
|
$list = [];
|
|
while ($a = $stmt->fetch()) {
|
|
$list[] = [
|
|
'id' => (int)$a['id'],
|
|
'title' => $a['title'],
|
|
'pinned' => (int)$a['pinned'],
|
|
'excerpt' => mb_substr(strip_tags($a['content'] ?? ''), 0, 120),
|
|
'created_at' => $a['created_at'],
|
|
];
|
|
}
|
|
json_ok(['announcements' => $list]);
|
|
default:
|
|
json_err(404, '未知操作: ' . h($action));
|
|
}
|