163 lines
7.1 KiB
PHP
163 lines
7.1 KiB
PHP
<?php
|
|
require __DIR__ . '/includes/init.php';
|
|
$pageTitle = '商城首页';
|
|
$cat = trim($_GET['cat'] ?? '');
|
|
$q = trim($_GET['q'] ?? '');
|
|
$where = ['status = 1'];
|
|
$params = [];
|
|
if ($cat !== '') { $where[] = 'category = ?'; $params[] = $cat; }
|
|
if ($q !== '') { $where[] = 'name LIKE ?'; $params[] = '%' . $q . '%'; }
|
|
$sql = 'SELECT * FROM ' . tn('products') . ' WHERE ' . implode(' AND ', $where) . ' ORDER BY created_at DESC';
|
|
$stmt = db()->prepare($sql);
|
|
$stmt->execute($params);
|
|
$products = $stmt->fetchAll();
|
|
$anns = db()->query(
|
|
'SELECT * FROM ' . tn('announcements') . ' WHERE status = 1 ORDER BY pinned DESC, created_at DESC LIMIT 3'
|
|
)->fetchAll();
|
|
$groups = db()->query('SELECT name, icon FROM ' . tn('product_groups') . ' ORDER BY sort_order ASC, name ASC')->fetchAll();
|
|
$categories = array_column($groups, 'name');
|
|
$about = getSetting('mall_about', '');
|
|
require 'includes/header.php';
|
|
?>
|
|
<section class="hero">
|
|
<div class="hero-content">
|
|
<h1><i class="fas fa-store"></i> <?= __('hero.title') ?></h1>
|
|
<p><?= __('hero.subtitle') ?></p>
|
|
<form class="search-bar" method="get" action="/" id="searchForm">
|
|
<input type="text" name="q" value="<?= h($q) ?>" placeholder="<?= __('common.search_placeholder') ?>" id="searchInput" autocomplete="off">
|
|
<button type="submit" class="btn btn-primary"><i class="fas fa-search"></i></button>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
<?php if (!empty($anns)): ?>
|
|
<section class="section ann-strip-wrap">
|
|
<div class="ann-strip">
|
|
<span class="ann-strip-label"><i class="fas fa-bullhorn"></i> <?= __('index.announcement') ?></span>
|
|
<div class="ann-strip-items">
|
|
<?php foreach ($anns as $a): ?>
|
|
<a class="ann-strip-item" href="announcement?id=<?= (int)$a['id'] ?>">
|
|
<?php if (!empty($a['pinned'])): ?><i class="fas fa-thumbtack ann-pin"></i><?php endif; ?>
|
|
<span class="ann-strip-title"><?= h($a['title']) ?></span>
|
|
<span class="ann-strip-date"><?= date('m-d', strtotime($a['created_at'])) ?></span>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<a href="announcements" class="ann-strip-more"><?= __('index.more') ?> <i class="fas fa-chevron-right"></i></a>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($about !== ''): ?>
|
|
<section class="section mall-about">
|
|
<h2 class="sec-title"><i class="fas fa-info-circle"></i> <?= __('product.about_mall') ?></h2>
|
|
<div class="about-card">
|
|
<p><?= nl2br(h($about)) ?></p>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
|
|
<section id="products" class="section">
|
|
<div class="cat-chips">
|
|
<a href="/" class="chip <?= $cat===''?'active':'' ?>"><i class="fas fa-th-large"></i> <?= __('index.all') ?></a>
|
|
<?php foreach ($groups as $g): ?>
|
|
<a href="/?cat=<?= urlencode($g['name']) ?>" class="chip <?= $cat===$g['name']?'active':'' ?>">
|
|
<?php if (!empty($g['icon'])): ?><i class="fas <?= h($g['icon']) ?>"></i> <?php endif; ?><?= h($g['name']) ?>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<?php if (empty($products)): ?>
|
|
<p class="empty"><?= __('index.no_result') ?></p>
|
|
<?php else: ?>
|
|
<div class="product-grid" id="productGrid">
|
|
<?php foreach ($products as $p): ?>
|
|
<a class="product-card" href="product?id=<?= (int)$p['id'] ?>">
|
|
<div class="pc-media">
|
|
<?php if (!empty($p['image'])): ?>
|
|
<img src="<?= h($p['image']) ?>" alt="<?= h($p['name']) ?>">
|
|
<?php else: ?>
|
|
<i class="fas <?= h($p['icon']) ?>"></i>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="pc-body">
|
|
<span class="pc-cat"><?= h($p['category']) ?></span>
|
|
<h3 class="pc-name"><?= h($p['name']) ?></h3>
|
|
<div class="pc-meta">
|
|
<?php if (!empty($p['points_price'])): ?>
|
|
<span class="pc-price"><?= (int)$p['points_price'] ?> <?= __('index.points_unit') ?></span>
|
|
<?php else: ?>
|
|
<span class="pc-price"><?= __('product.cash_price') ?></span>
|
|
<?php endif; ?>
|
|
<span class="pc-stock"><?= __('common.remaining') ?> <?= (int)$p['stock'] ?> <?= __('common.items') ?></span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<?php require 'includes/footer.php'; ?>
|
|
<script>
|
|
// AJAX 搜索增强:输入时实时搜索(防抖 400ms),回车/提交仍走表单刷新
|
|
(function () {
|
|
var I18N = {
|
|
noResult: '<?= __('index.no_result') ?>',
|
|
pointsUnit: '<?= __('index.points_unit') ?>',
|
|
cashPrice: '<?= __('product.cash_price') ?>',
|
|
remaining: '<?= __('common.remaining') ?>',
|
|
items: '<?= __('common.items') ?>'
|
|
};
|
|
var input = document.getElementById('searchInput');
|
|
var grid = document.getElementById('productGrid');
|
|
if (!input || !grid) return;
|
|
|
|
var timer = null;
|
|
input.addEventListener('input', function () {
|
|
var q = input.value.trim();
|
|
clearTimeout(timer);
|
|
if (q.length < 1) { return; } // 太短不搜索,保留原有结果
|
|
timer = setTimeout(function () {
|
|
Fnw.request({
|
|
action: 'search',
|
|
data: { q: q },
|
|
success: function (res) {
|
|
renderProducts(res.data.products);
|
|
},
|
|
error: function () {
|
|
// 静默失败,用户仍可回车提交
|
|
}
|
|
});
|
|
}, 400);
|
|
});
|
|
|
|
function renderProducts(products) {
|
|
if (!products || products.length === 0) {
|
|
grid.innerHTML = '<p class="empty">' + I18N.noResult + '</p>';
|
|
return;
|
|
}
|
|
var html = '';
|
|
for (var i = 0; i < products.length; i++) {
|
|
var p = products[i];
|
|
var media = p.image
|
|
? '<img src="' + encodeURIComponent(p.image) + '" alt="' + p.name + '">'
|
|
: '<i class="fas ' + (p.icon || 'fa-box') + '"></i>';
|
|
var price = p.points_price !== null && p.points_price !== ''
|
|
? '<span class="pc-price">' + p.points_price + ' ' + I18N.pointsUnit + '</span>'
|
|
: '<span class="pc-price">' + I18N.cashPrice + '</span>';
|
|
html += '<a class="product-card" href="product?id=' + p.id + '">' +
|
|
'<div class="pc-media">' + media + '</div>' +
|
|
'<div class="pc-body">' +
|
|
'<span class="pc-cat">' + (p.category || '') + '</span>' +
|
|
'<h3 class="pc-name">' + p.name + '</h3>' +
|
|
'<div class="pc-meta">' +
|
|
price +
|
|
'<span class="pc-stock">' + I18N.remaining + ' ' + p.stock + ' ' + I18N.items + '</span>' +
|
|
'</div>' +
|
|
'</div></a>';
|
|
}
|
|
grid.innerHTML = html;
|
|
}
|
|
})();
|
|
</script>
|