2.1 版本文件

This commit is contained in:
2026-08-14 10:55:06 +08:00
commit a3434ec620
67 changed files with 12559 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
<?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>