|
|
|
@ -0,0 +1,889 @@
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
if (!defined('ABSPATH')) {
|
|
|
|
|
|
|
|
exit;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
global $products_list_shortcode_atts;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$atts = $products_list_shortcode_atts;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$per_page = intval($atts['per_page']);
|
|
|
|
|
|
|
|
$category = sanitize_text_field($atts['category']);
|
|
|
|
|
|
|
|
$order = sanitize_text_field($atts['order']);
|
|
|
|
|
|
|
|
$orderby = sanitize_text_field($atts['orderby']);
|
|
|
|
|
|
|
|
$category_order = sanitize_text_field($atts['category_order']);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$meta_query = array();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!empty($category) && $category !== 'all') {
|
|
|
|
|
|
|
|
$meta_query[] = array(
|
|
|
|
|
|
|
|
'key' => 'cate_type',
|
|
|
|
|
|
|
|
'value' => $category,
|
|
|
|
|
|
|
|
'compare' => 'LIKE'
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$query_args = array(
|
|
|
|
|
|
|
|
'post_type' => 'products',
|
|
|
|
|
|
|
|
'posts_per_page' => $per_page,
|
|
|
|
|
|
|
|
'paged' => get_query_var('paged') ? get_query_var('paged') : 1,
|
|
|
|
|
|
|
|
'meta_query' => $meta_query,
|
|
|
|
|
|
|
|
'order' => $order,
|
|
|
|
|
|
|
|
'orderby' => $orderby,
|
|
|
|
|
|
|
|
'post_status' => 'publish'
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$products_query = new WP_Query($query_args);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$all_products_args = array(
|
|
|
|
|
|
|
|
'post_type' => 'products',
|
|
|
|
|
|
|
|
'posts_per_page' => -1,
|
|
|
|
|
|
|
|
'post_status' => 'publish'
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$all_products = get_posts($all_products_args);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$unique_cate_types = array();
|
|
|
|
|
|
|
|
foreach ($all_products as $product) {
|
|
|
|
|
|
|
|
$cate_type = get_post_meta($product->ID, 'cate_type', true);
|
|
|
|
|
|
|
|
if (!empty($cate_type)) {
|
|
|
|
|
|
|
|
$cate_types = explode(',', $cate_type);
|
|
|
|
|
|
|
|
foreach ($cate_types as $type) {
|
|
|
|
|
|
|
|
$type = trim($type);
|
|
|
|
|
|
|
|
if (!empty($type) && !in_array($type, $unique_cate_types)) {
|
|
|
|
|
|
|
|
$unique_cate_types[] = $type;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!empty($category_order)) {
|
|
|
|
|
|
|
|
$category_order_array = explode(',', $category_order);
|
|
|
|
|
|
|
|
$category_order_array = array_map('trim', $category_order_array);
|
|
|
|
|
|
|
|
$unique_cate_types = array_filter($unique_cate_types, function($type) use ($category_order_array) {
|
|
|
|
|
|
|
|
return in_array($type, $category_order_array);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
$unique_cate_types = array_intersect($category_order_array, $unique_cate_types);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$total_products = count($all_products);
|
|
|
|
|
|
|
|
?>
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
|
|
/* Custom scrollbar for horizontal scrolling elements */
|
|
|
|
|
|
|
|
.hide-scrollbar::-webkit-scrollbar {
|
|
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.hide-scrollbar {
|
|
|
|
|
|
|
|
-ms-overflow-style: none;
|
|
|
|
|
|
|
|
scrollbar-width: none;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Smooth Scrolling */
|
|
|
|
|
|
|
|
html {
|
|
|
|
|
|
|
|
scroll-behavior: smooth;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Card Hover Effects */
|
|
|
|
|
|
|
|
.product-card {
|
|
|
|
|
|
|
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
|
|
|
|
position: relative; /* 确保 z-index 生效 */
|
|
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.product-card:hover {
|
|
|
|
|
|
|
|
transform: translateY(-8px);
|
|
|
|
|
|
|
|
z-index: 30; /* 低于 filter 的 40 */
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
<main class="max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8 py-8 flex flex-col flex-1 min-h-screen">
|
|
|
|
|
|
|
|
<div class="max-w-4xl mb-10" data-aos="fade-right" data-aos-delay="200">
|
|
|
|
|
|
|
|
<h1 class="font-display text-[#111318] dark:text-white text-4xl md:text-5xl font-black leading-tight tracking-tight mb-4 bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-600 dark:from-white dark:to-gray-400">
|
|
|
|
|
|
|
|
High-Performance Energy Storage
|
|
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
|
|
<p class="text-[#5f6e8c] dark:text-gray-400 text-lg font-normal leading-relaxed max-w-2xl">
|
|
|
|
|
|
|
|
From residential backups to utility-scale grids. Explore our LFP and Solid-State battery solutions designed for maximum efficiency.
|
|
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div id="filter-sticky-wrapper" class="sticky top-[80px] z-40 mb-8 transition-all duration-300 -mx-4 px-4 sm:mx-0 sm:px-0 rounded-b-xl">
|
|
|
|
|
|
|
|
<div class="absolute inset-0 bg-background-light/95 dark:bg-background-dark/95 backdrop-blur-md transition-opacity duration-300 opacity-90 border-b border-transparent" id="filter-bg"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="relative max-w-[1280px] mx-auto py-3">
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between mb-2 px-1">
|
|
|
|
|
|
|
|
<span class="text-[10px] font-extrabold tracking-widest text-gray-500 dark:text-gray-400 uppercase flex items-center gap-1">
|
|
|
|
|
|
|
|
<span class="material-symbols-outlined text-[14px]"></span>
|
|
|
|
|
|
|
|
Filter by Category
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<div class="hidden sm:flex gap-2">
|
|
|
|
|
|
|
|
<span class="text-xs text-gray-400 font-mono" id="item-count-display">Showing <?php echo esc_html($total_products); ?> items</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="relative group/filters">
|
|
|
|
|
|
|
|
<div class="absolute left-0 top-0 bottom-0 w-8 bg-gradient-to-r from-[#f8fcfa] dark:from-[#10221c] to-transparent z-10 pointer-events-none md:hidden transition-colors duration-300" id="mask-left"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="flex overflow-x-auto md:flex-wrap gap-3 py-2 -my-2 px-2 -mx-2 hide-scrollbar scroll-smooth snap-x items-center">
|
|
|
|
|
|
|
|
<button onclick="filterSelection('all', this)" class="filter-btn active snap-start shrink-0 relative flex items-center gap-2 pl-4 pr-5 h-10 rounded-full border transition-all duration-300 group" data-filter="all">
|
|
|
|
|
|
|
|
<span class="font-bold text-sm tracking-wide">All Systems</span>
|
|
|
|
|
|
|
|
<span class="count-badge flex items-center justify-center h-5 min-w-[20px] px-1.5 rounded-full text-[10px] font-bold"><?php echo esc_html($total_products); ?></span>
|
|
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<?php foreach ($unique_cate_types as $cate_type): ?>
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
$cate_type_value = get_post_meta_by_key('cate_type_value', $cate_type);
|
|
|
|
|
|
|
|
$display_name = !empty($cate_type_value) ? $cate_type_value : $cate_type;
|
|
|
|
|
|
|
|
$count = 0;
|
|
|
|
|
|
|
|
foreach ($all_products as $product) {
|
|
|
|
|
|
|
|
$product_cate_type = get_post_meta($product->ID, 'cate_type', true);
|
|
|
|
|
|
|
|
if (!empty($product_cate_type) && strpos($product_cate_type, $cate_type) !== false) {
|
|
|
|
|
|
|
|
$count++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
?>
|
|
|
|
|
|
|
|
<button onclick="filterSelection('<?php echo esc_js($cate_type); ?>', this)" class="filter-btn snap-start shrink-0 flex items-center gap-2 pl-4 pr-4 h-10 rounded-full border border-[#e5e7eb] dark:border-[#374151] bg-white dark:bg-[#1f2937] text-[#5f6e8c] dark:text-gray-300 transition-all duration-300 hover:scale-105 active:scale-95 hover:shadow-lg hover:border-primary z-0 hover:z-10" data-filter="<?php echo esc_attr($cate_type); ?>">
|
|
|
|
|
|
|
|
<span class="font-medium text-sm"><?php echo esc_html($display_name); ?></span>
|
|
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="absolute right-0 top-0 bottom-0 w-12 bg-gradient-to-l from-[#f8fcfa] dark:from-[#10221c] to-transparent z-10 pointer-events-none md:hidden transition-colors duration-300" id="mask-right"></div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div id="initial-loader" class="py-12 w-full">
|
|
|
|
|
|
|
|
<div class="flex flex-col items-center justify-center space-y-4 w-full">
|
|
|
|
|
|
|
|
<div class="relative flex-shrink-0">
|
|
|
|
|
|
|
|
<div class="w-16 h-16 border-4 border-primary/20 border-t-primary rounded-full animate-spin"></div>
|
|
|
|
|
|
|
|
<div class="absolute inset-0 flex items-center justify-center">
|
|
|
|
|
|
|
|
<span class="material-symbols-outlined text-2xl text-primary animate-pulse"></span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
|
|
<p class="text-gray-500 dark:text-gray-400 font-medium">Loading products...</p>
|
|
|
|
|
|
|
|
<p class="text-gray-400 dark:text-gray-500 text-sm mt-1">Please wait</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div id="product-grid" class="columns-1 md:columns-2 lg:columns-3 gap-6 pb-12">
|
|
|
|
|
|
|
|
<?php if ($products_query->have_posts()): ?>
|
|
|
|
|
|
|
|
<?php while ($products_query->have_posts()): $products_query->the_post(); ?>
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
$post_id = get_the_ID();
|
|
|
|
|
|
|
|
$cate_type = get_post_meta($post_id, 'cate_type', true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$cover_image = get_the_post_thumbnail_url($post_id, 'product-cover-medium');
|
|
|
|
|
|
|
|
if (empty($cover_image)) {
|
|
|
|
|
|
|
|
$cover_image = get_post_meta($post_id, '_product_banner_url', true);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($cover_image)) {
|
|
|
|
|
|
|
|
$cover_image = get_template_directory_uri() . '/assets/images/products-1.webp';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$usage_scenario_data = get_post_meta($post_id, '_usage_scenario_data', true);
|
|
|
|
|
|
|
|
$description = '';
|
|
|
|
|
|
|
|
if (!empty($usage_scenario_data) && isset($usage_scenario_data['bottom_text'])) {
|
|
|
|
|
|
|
|
$description = $usage_scenario_data['bottom_text'];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
$attr_keys = get_post_meta($post_id, 'attr_key', true);
|
|
|
|
|
|
|
|
$attr_values = get_post_meta($post_id, 'attr_value', true);
|
|
|
|
|
|
|
|
$attr_keys_array = !empty($attr_keys) ? explode(',', $attr_keys) : array();
|
|
|
|
|
|
|
|
$attr_values_array = !empty($attr_values) ? explode(',', $attr_values) : array();
|
|
|
|
|
|
|
|
$attrs = array();
|
|
|
|
|
|
|
|
for ($i = 0; $i < min(4, count($attr_keys_array)); $i++) {
|
|
|
|
|
|
|
|
if (isset($attr_keys_array[$i]) && isset($attr_values_array[$i])) {
|
|
|
|
|
|
|
|
$attrs[] = array(
|
|
|
|
|
|
|
|
'key' => trim($attr_keys_array[$i]),
|
|
|
|
|
|
|
|
'value' => trim($attr_values_array[$i])
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
$efficiency = get_post_meta($post_id, 'efficiency', true);
|
|
|
|
|
|
|
|
$is_new = get_post_meta($post_id, 'is_new', true);
|
|
|
|
|
|
|
|
$is_new_value = filter_var($is_new, FILTER_VALIDATE_BOOLEAN);
|
|
|
|
|
|
|
|
?>
|
|
|
|
|
|
|
|
<div data-category="<?php echo esc_attr($cate_type); ?>" class="product-card break-inside-avoid bg-white dark:bg-card-dark rounded-xl overflow-hidden border border-[#e5e7eb] dark:border-[#2a3441] shadow-sm hover:shadow-card-hover hover:border-primary/40 group" data-aos="fade-up" data-aos-delay="0">
|
|
|
|
|
|
|
|
<div class="skeleton-wrapper relative h-64 w-full bg-gradient-to-b from-[#787878] to-white dark:from-[#333] dark:to-[#111318] flex items-center justify-center p-6 transition-all duration-500 group-hover:from-[#666666] group-hover:to-[#e6fcf5]">
|
|
|
|
|
|
|
|
<?php if (!empty($is_new)): ?>
|
|
|
|
|
|
|
|
<div class="absolute top-4 left-4 bg-primary text-white text-xs font-bold px-2.5 py-1 rounded shadow-sm z-10">
|
|
|
|
|
|
|
|
NEW
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
<?php if (!empty($efficiency)): ?>
|
|
|
|
|
|
|
|
<div class="absolute top-4 right-4 bg-white/90 dark:bg-[#2a3441]/90 backdrop-blur text-primary text-xs font-bold px-2.5 py-1 rounded shadow-sm border border-primary/10 z-10">
|
|
|
|
|
|
|
|
<?php echo esc_html($efficiency); ?>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
<div class="lazy-target w-full h-full bg-center bg-no-repeat bg-contain transform group-hover:scale-105 transition-transform duration-500" data-alt="<?php echo esc_attr(get_the_title()); ?>" data-bg="<?php echo esc_url($cover_image); ?>" style="background-image: url('<?php echo esc_url($cover_image); ?>');"></div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="p-5">
|
|
|
|
|
|
|
|
<div class="flex justify-between items-start mb-2">
|
|
|
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
|
|
|
<h3 class="text-xl font-bold font-display text-[#111318] dark:text-white group-hover:text-primary transition-colors"><?php echo esc_html(get_the_title()); ?></h3>
|
|
|
|
|
|
|
|
<?php if ($is_new_value): ?>
|
|
|
|
|
|
|
|
<span class="bg-primary/10 text-primary text-[10px] font-bold px-2 py-0.5 rounded-full border border-primary/20 animate-pulse">NEW</span>
|
|
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-y-3 gap-x-4 mb-5 text-sm">
|
|
|
|
|
|
|
|
<?php foreach ($attrs as $attr): ?>
|
|
|
|
|
|
|
|
<div class="bg-gray-50 dark:bg-[#1f2937] p-2 rounded <?php echo $is_new_value ? 'border-l-2 border-primary' : ''; ?>">
|
|
|
|
|
|
|
|
<p class="text-[#5f6e8c] dark:text-gray-500 text-[10px] uppercase tracking-wider"><?php echo esc_html($attr['key']); ?></p>
|
|
|
|
|
|
|
|
<p class="font-semibold dark:text-gray-200"><?php echo esc_html($attr['value']); ?></p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<p class="text-[#5f6e8c] dark:text-gray-400 text-sm leading-relaxed mb-6 line-clamp-4">
|
|
|
|
|
|
|
|
<?php echo wp_kses_post($description); ?>
|
|
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<a href="<?php echo esc_url(get_permalink()); ?>" class="product-detail-link">
|
|
|
|
|
|
|
|
<button class="w-full flex items-center justify-center gap-2 h-11 rounded-lg bg-[#f0f1f5] dark:bg-[#2a3441] text-[#111318] dark:text-white text-sm font-bold hover:bg-[#e0e2e8] dark:hover:bg-[#374151] transition-all group-hover:bg-primary group-hover:text-white group-hover:shadow-glow">
|
|
|
|
|
|
|
|
<span>View Datasheet</span>
|
|
|
|
|
|
|
|
<span class="material-symbols-outlined text-[18px] group-hover:translate-x-1 transition-transform"></span>
|
|
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
</a>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<?php endwhile; ?>
|
|
|
|
|
|
|
|
<?php wp_reset_postdata(); ?>
|
|
|
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
|
|
<div class="col-span-full text-center py-12">
|
|
|
|
|
|
|
|
<p class="text-gray-500 dark:text-gray-400">No products found.</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<?php if ($products_query->max_num_pages > 1): ?>
|
|
|
|
|
|
|
|
<div class="flex justify-center pt-8 pb-16" data-aos="zoom-in" data-aos-offset="50">
|
|
|
|
|
|
|
|
<button id="load-more-btn" class="relative group flex items-center justify-center px-8 py-3 rounded-full bg-primary/10 hover:bg-primary/20 dark:bg-primary/20 dark:hover:bg-primary/30 transition-all duration-300 active:scale-95" data-page="1" data-max-pages="<?php echo esc_attr($products_query->max_num_pages); ?>" data-per-page="<?php echo esc_attr($per_page); ?>">
|
|
|
|
|
|
|
|
<div class="absolute inset-0 rounded-full border border-primary opacity-20 animate-pulse group-hover:opacity-40"></div>
|
|
|
|
|
|
|
|
<span class="text-primary font-bold text-sm tracking-wide group-hover:scale-105 transition-transform">LOAD MORE PRODUCTS</span>
|
|
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 产品列表状态管理器
|
|
|
|
|
|
|
|
* 负责管理产品列表的状态保存、恢复和清理
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
const ProductListStateManager = {
|
|
|
|
|
|
|
|
// 状态键名常量
|
|
|
|
|
|
|
|
KEYS: {
|
|
|
|
|
|
|
|
GRID_HTML: 'products_grid_html',
|
|
|
|
|
|
|
|
SCROLL_POSITION: 'products_scroll_position',
|
|
|
|
|
|
|
|
CURRENT_PAGE: 'products_current_page',
|
|
|
|
|
|
|
|
CURRENT_CATEGORY: 'products_current_category',
|
|
|
|
|
|
|
|
LOAD_MORE_VISIBLE: 'products_load_more_visible',
|
|
|
|
|
|
|
|
NAVIGATED_FROM_LIST: 'products_navigated_from_list',
|
|
|
|
|
|
|
|
TIMESTAMP: 'products_state_timestamp'
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 状态过期时间(30分钟)
|
|
|
|
|
|
|
|
EXPIRY_TIME: 30 * 60 * 1000,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 保存当前状态
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
save: function() {
|
|
|
|
|
|
|
|
console.log('[StateManager] Saving state...');
|
|
|
|
|
|
|
|
const grid = document.getElementById('product-grid');
|
|
|
|
|
|
|
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!grid) {
|
|
|
|
|
|
|
|
console.error('[StateManager] Grid element not found!');
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[StateManager] Current global variables:', {
|
|
|
|
|
|
|
|
'window.currentPage': window.currentPage,
|
|
|
|
|
|
|
|
'window.currentCategory': window.currentCategory,
|
|
|
|
|
|
|
|
'currentPage (local)': currentPage,
|
|
|
|
|
|
|
|
'currentCategory (local)': currentCategory
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const state = {
|
|
|
|
|
|
|
|
GRID_HTML: grid.innerHTML,
|
|
|
|
|
|
|
|
SCROLL_POSITION: window.scrollY,
|
|
|
|
|
|
|
|
CURRENT_PAGE: window.currentPage || currentPage || 1,
|
|
|
|
|
|
|
|
CURRENT_CATEGORY: window.currentCategory || currentCategory || 'all',
|
|
|
|
|
|
|
|
LOAD_MORE_VISIBLE: loadMoreBtn ? loadMoreBtn.style.display !== 'none' : false,
|
|
|
|
|
|
|
|
NAVIGATED_FROM_LIST: 'true',
|
|
|
|
|
|
|
|
TIMESTAMP: Date.now()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[StateManager] State to save:', state);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 保存到 sessionStorage - 直接遍历 state 对象
|
|
|
|
|
|
|
|
Object.keys(state).forEach(key => {
|
|
|
|
|
|
|
|
const storageKey = this.KEYS[key.toUpperCase()] || this.KEYS[key];
|
|
|
|
|
|
|
|
const value = state[key];
|
|
|
|
|
|
|
|
console.log('[StateManager] Saving key:', key, '->', storageKey, '=', value);
|
|
|
|
|
|
|
|
if (value !== undefined && storageKey) {
|
|
|
|
|
|
|
|
sessionStorage.setItem(storageKey, typeof value === 'object' ? JSON.stringify(value) : value);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[StateManager] State saved:', {
|
|
|
|
|
|
|
|
productCount: grid.querySelectorAll('.product-card').length,
|
|
|
|
|
|
|
|
currentPage: state.currentPage,
|
|
|
|
|
|
|
|
currentCategory: state.currentCategory,
|
|
|
|
|
|
|
|
scrollPosition: state.scrollPosition,
|
|
|
|
|
|
|
|
loadMoreVisible: state.loadMoreVisible
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 恢复状态
|
|
|
|
|
|
|
|
* @returns {boolean} 是否成功恢复
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
restore: function() {
|
|
|
|
|
|
|
|
console.log('[StateManager] Restoring state...');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const gridHtml = sessionStorage.getItem(this.KEYS.GRID_HTML);
|
|
|
|
|
|
|
|
const navigatedFromList = sessionStorage.getItem(this.KEYS.NAVIGATED_FROM_LIST);
|
|
|
|
|
|
|
|
const timestamp = sessionStorage.getItem(this.KEYS.TIMESTAMP);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查状态是否存在且未过期
|
|
|
|
|
|
|
|
if (!gridHtml || navigatedFromList !== 'true') {
|
|
|
|
|
|
|
|
console.log('[StateManager] No valid state to restore');
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查状态是否过期
|
|
|
|
|
|
|
|
if (timestamp && (Date.now() - parseInt(timestamp)) > this.EXPIRY_TIME) {
|
|
|
|
|
|
|
|
console.log('[StateManager] State expired, clearing...');
|
|
|
|
|
|
|
|
this.clear();
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const grid = document.getElementById('product-grid');
|
|
|
|
|
|
|
|
if (!grid) {
|
|
|
|
|
|
|
|
console.error('[StateManager] Grid element not found!');
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复产品列表
|
|
|
|
|
|
|
|
grid.innerHTML = gridHtml;
|
|
|
|
|
|
|
|
grid.style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏初始加载器
|
|
|
|
|
|
|
|
const initialLoader = document.getElementById('initial-loader');
|
|
|
|
|
|
|
|
if (initialLoader) {
|
|
|
|
|
|
|
|
initialLoader.style.display = 'none';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复全局变量和局部变量
|
|
|
|
|
|
|
|
window.currentPage = parseInt(sessionStorage.getItem(this.KEYS.CURRENT_PAGE)) || 1;
|
|
|
|
|
|
|
|
window.currentCategory = sessionStorage.getItem(this.KEYS.CURRENT_CATEGORY) || 'all';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 同步更新局部变量
|
|
|
|
|
|
|
|
currentPage = window.currentPage;
|
|
|
|
|
|
|
|
currentCategory = window.currentCategory;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[StateManager] Restored variables:', {
|
|
|
|
|
|
|
|
'window.currentPage': window.currentPage,
|
|
|
|
|
|
|
|
'window.currentCategory': window.currentCategory,
|
|
|
|
|
|
|
|
'currentPage (local)': currentPage,
|
|
|
|
|
|
|
|
'currentCategory (local)': currentCategory
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复加载更多按钮状态
|
|
|
|
|
|
|
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
|
|
|
|
|
|
|
const loadMoreVisible = sessionStorage.getItem(this.KEYS.LOAD_MORE_VISIBLE) === 'true';
|
|
|
|
|
|
|
|
if (loadMoreBtn) {
|
|
|
|
|
|
|
|
const loadMoreContainer = loadMoreBtn.parentElement;
|
|
|
|
|
|
|
|
if (loadMoreVisible) {
|
|
|
|
|
|
|
|
loadMoreBtn.style.display = 'flex';
|
|
|
|
|
|
|
|
loadMoreBtn.disabled = false;
|
|
|
|
|
|
|
|
if (loadMoreContainer) loadMoreContainer.style.display = 'flex';
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
loadMoreBtn.style.display = 'none';
|
|
|
|
|
|
|
|
if (loadMoreContainer) loadMoreContainer.style.display = 'none';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复分类按钮样式
|
|
|
|
|
|
|
|
const savedCategory = sessionStorage.getItem(this.KEYS.CURRENT_CATEGORY);
|
|
|
|
|
|
|
|
if (savedCategory) {
|
|
|
|
|
|
|
|
const activeBtn = document.querySelector(`[data-filter="${savedCategory}"]`);
|
|
|
|
|
|
|
|
if (activeBtn) {
|
|
|
|
|
|
|
|
updateButtonStyles(activeBtn);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 更新产品计数
|
|
|
|
|
|
|
|
const visibleCount = grid.querySelectorAll('.product-card').length;
|
|
|
|
|
|
|
|
updateItemCount(visibleCount);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新动画和图片
|
|
|
|
|
|
|
|
if (typeof AOS !== 'undefined') {
|
|
|
|
|
|
|
|
AOS.refresh();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof window.ImageLoader !== 'undefined' && typeof window.ImageLoader.scan === 'function') {
|
|
|
|
|
|
|
|
window.ImageLoader.scan();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[StateManager] State restored:', {
|
|
|
|
|
|
|
|
productCount: visibleCount,
|
|
|
|
|
|
|
|
currentPage: window.currentPage,
|
|
|
|
|
|
|
|
currentCategory: window.currentCategory
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 恢复滚动位置
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
restoreScrollPosition: function() {
|
|
|
|
|
|
|
|
const scrollPosition = sessionStorage.getItem(this.KEYS.SCROLL_POSITION);
|
|
|
|
|
|
|
|
if (scrollPosition) {
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
console.log('[StateManager] Restoring scroll position:', scrollPosition);
|
|
|
|
|
|
|
|
window.scrollTo({
|
|
|
|
|
|
|
|
top: parseInt(scrollPosition),
|
|
|
|
|
|
|
|
behavior: 'auto'
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 清除所有状态
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
clear: function() {
|
|
|
|
|
|
|
|
console.log('[StateManager] Clearing all state...');
|
|
|
|
|
|
|
|
Object.values(this.KEYS).forEach(key => {
|
|
|
|
|
|
|
|
sessionStorage.removeItem(key);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 检查是否有有效的状态
|
|
|
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
hasValidState: function() {
|
|
|
|
|
|
|
|
const gridHtml = sessionStorage.getItem(this.KEYS.GRID_HTML);
|
|
|
|
|
|
|
|
const navigatedFromList = sessionStorage.getItem(this.KEYS.NAVIGATED_FROM_LIST);
|
|
|
|
|
|
|
|
const timestamp = sessionStorage.getItem(this.KEYS.TIMESTAMP);
|
|
|
|
|
|
|
|
console.log('[StateManager] Checking valid state:', {
|
|
|
|
|
|
|
|
gridHtml: gridHtml,
|
|
|
|
|
|
|
|
navigatedFromList: navigatedFromList,
|
|
|
|
|
|
|
|
timestamp: timestamp
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!gridHtml || navigatedFromList !== 'true') {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否过期
|
|
|
|
|
|
|
|
if (timestamp && (Date.now() - parseInt(timestamp)) > this.EXPIRY_TIME) {
|
|
|
|
|
|
|
|
this.clear();
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 全局变量
|
|
|
|
|
|
|
|
let currentCategory = 'all';
|
|
|
|
|
|
|
|
let currentPage = 1;
|
|
|
|
|
|
|
|
let perPage = 6;
|
|
|
|
|
|
|
|
let originalLoadMoreBtnContent = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const activeClass = "bg-[#111318] dark:bg-white text-white dark:text-[#111318] border-transparent shadow-lg shadow-gray-200 dark:shadow-none ring-2 ring-offset-2 ring-transparent";
|
|
|
|
|
|
|
|
const inactiveClass = "bg-white dark:bg-[#1f2937] text-[#5f6e8c] dark:text-gray-300 border-[#e5e7eb] dark:border-[#374151]";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 保存当前状态(兼容旧代码)
|
|
|
|
|
|
|
|
* @returns {boolean} 是否成功保存
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
function saveState() {
|
|
|
|
|
|
|
|
return ProductListStateManager.save();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateButtonStyles(activeBtn) {
|
|
|
|
|
|
|
|
const filterBtns = document.querySelectorAll('.filter-btn');
|
|
|
|
|
|
|
|
filterBtns.forEach(btn => {
|
|
|
|
|
|
|
|
btn.className = btn.className.replace(activeClass, "").replace(inactiveClass, "");
|
|
|
|
|
|
|
|
btn.classList.add(...inactiveClass.split(" "));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const badge = btn.querySelector('.count-badge');
|
|
|
|
|
|
|
|
if(badge) {
|
|
|
|
|
|
|
|
badge.className = "count-badge hidden";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
activeBtn.classList.remove(...inactiveClass.split(" "));
|
|
|
|
|
|
|
|
activeBtn.classList.add(...activeClass.split(" "));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const activeBadge = activeBtn.querySelector('.count-badge');
|
|
|
|
|
|
|
|
if(activeBadge) {
|
|
|
|
|
|
|
|
activeBadge.className = "count-badge flex items-center justify-center h-5 min-w-[20px] px-1.5 rounded-full bg-white/20 dark:bg-black/10 text-[10px] font-bold ml-auto";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateItemCount(count) {
|
|
|
|
|
|
|
|
const itemCountDisplay = document.getElementById('item-count-display');
|
|
|
|
|
|
|
|
if(itemCountDisplay) {
|
|
|
|
|
|
|
|
itemCountDisplay.textContent = `Showing ${count} items`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function loadProducts(category, page = 1, append = false) {
|
|
|
|
|
|
|
|
const grid = document.getElementById('product-grid');
|
|
|
|
|
|
|
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
|
|
|
|
|
|
|
const initialLoader = document.getElementById('initial-loader');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
currentCategory = category;
|
|
|
|
|
|
|
|
window.currentCategory = category;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!append) {
|
|
|
|
|
|
|
|
if (initialLoader) {
|
|
|
|
|
|
|
|
initialLoader.style.display = 'flex';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
grid.style.display = 'none';
|
|
|
|
|
|
|
|
grid.innerHTML = '';
|
|
|
|
|
|
|
|
} else if (loadMoreBtn) {
|
|
|
|
|
|
|
|
loadMoreBtn.innerHTML = `
|
|
|
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
|
|
|
<span class="material-symbols-outlined animate-spin"></span>
|
|
|
|
|
|
|
|
<span>Loading...</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
loadMoreBtn.disabled = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const offset = (page - 1) * perPage;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[loadProducts] Request parameters:', {
|
|
|
|
|
|
|
|
category: category,
|
|
|
|
|
|
|
|
page: page,
|
|
|
|
|
|
|
|
perPage: perPage,
|
|
|
|
|
|
|
|
offset: offset,
|
|
|
|
|
|
|
|
append: append,
|
|
|
|
|
|
|
|
'currentPage (local)': currentPage,
|
|
|
|
|
|
|
|
'window.currentPage': window.currentPage
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
|
|
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
body: new URLSearchParams({
|
|
|
|
|
|
|
|
action: 'load_more_products',
|
|
|
|
|
|
|
|
offset: offset,
|
|
|
|
|
|
|
|
per_page: perPage,
|
|
|
|
|
|
|
|
category: category,
|
|
|
|
|
|
|
|
nonce: '<?php echo wp_create_nonce('load_more_products_nonce'); ?>'
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.then(response => response.json())
|
|
|
|
|
|
|
|
.then(data => {
|
|
|
|
|
|
|
|
console.log('AJAX response:', data);
|
|
|
|
|
|
|
|
console.log('Append mode:', append);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
|
|
console.log('Data success is true');
|
|
|
|
|
|
|
|
if (!append) {
|
|
|
|
|
|
|
|
grid.innerHTML = '';
|
|
|
|
|
|
|
|
grid.style.display = 'block';
|
|
|
|
|
|
|
|
if (initialLoader) {
|
|
|
|
|
|
|
|
initialLoader.style.display = 'none';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
currentPage = 1;
|
|
|
|
|
|
|
|
window.currentPage = 1;
|
|
|
|
|
|
|
|
sessionStorage.setItem('products_current_page', 1);
|
|
|
|
|
|
|
|
sessionStorage.setItem('products_current_category', category);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data = data.data || {};
|
|
|
|
|
|
|
|
console.log('Products data:', data.products);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (data.products && data.products.length > 0) {
|
|
|
|
|
|
|
|
console.log('Has products, adding to grid');
|
|
|
|
|
|
|
|
data.products.forEach(product => {
|
|
|
|
|
|
|
|
const tempDiv = document.createElement('div');
|
|
|
|
|
|
|
|
tempDiv.innerHTML = product.html;
|
|
|
|
|
|
|
|
const newCard = tempDiv.firstElementChild;
|
|
|
|
|
|
|
|
newCard.setAttribute('data-aos-delay', '0');
|
|
|
|
|
|
|
|
grid.appendChild(newCard);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof AOS !== 'undefined') {
|
|
|
|
|
|
|
|
AOS.refresh();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof window.ImageLoader !== 'undefined' && typeof window.ImageLoader.scan === 'function') {
|
|
|
|
|
|
|
|
window.ImageLoader.scan();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('About to scroll, append:', append);
|
|
|
|
|
|
|
|
if (!append) {
|
|
|
|
|
|
|
|
console.log('Executing scroll logic');
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
const stickyWrapper = document.getElementById('filter-sticky-wrapper');
|
|
|
|
|
|
|
|
const headerOffset = 64 + (stickyWrapper ? stickyWrapper.offsetHeight : 60);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const gridRect = grid.getBoundingClientRect();
|
|
|
|
|
|
|
|
const targetPosition = gridRect.top + window.scrollY - headerOffset - 20;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('Scrolling to position:', targetPosition, 'Current scroll:', window.scrollY, 'Grid top:', gridRect.top);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
window.scrollTo({
|
|
|
|
|
|
|
|
top: targetPosition,
|
|
|
|
|
|
|
|
behavior: 'auto'
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}, 150);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
console.log('Skipping scroll, append mode');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const visibleCount = grid.querySelectorAll('.product-card').length;
|
|
|
|
|
|
|
|
updateItemCount(visibleCount);
|
|
|
|
|
|
|
|
console.log('Visible products:', visibleCount);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (loadMoreBtn) {
|
|
|
|
|
|
|
|
const loadMoreContainer = loadMoreBtn.parentElement;
|
|
|
|
|
|
|
|
if (loadMoreContainer) {
|
|
|
|
|
|
|
|
loadMoreContainer.style.display = 'flex';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (data.products.length < perPage) {
|
|
|
|
|
|
|
|
loadMoreBtn.style.display = 'none';
|
|
|
|
|
|
|
|
const loadMoreContainer = loadMoreBtn.parentElement;
|
|
|
|
|
|
|
|
if (loadMoreContainer) {
|
|
|
|
|
|
|
|
loadMoreContainer.style.display = 'none';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 更新当前页码并保存状态
|
|
|
|
|
|
|
|
currentPage = page;
|
|
|
|
|
|
|
|
window.currentPage = page;
|
|
|
|
|
|
|
|
window.currentCategory = currentCategory;
|
|
|
|
|
|
|
|
sessionStorage.setItem('products_current_page', page);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('Hide load more button - no more products, saving state');
|
|
|
|
|
|
|
|
saveState();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
loadMoreBtn.style.display = 'flex';
|
|
|
|
|
|
|
|
loadMoreBtn.innerHTML = originalLoadMoreBtnContent;
|
|
|
|
|
|
|
|
loadMoreBtn.disabled = false;
|
|
|
|
|
|
|
|
currentPage = page;
|
|
|
|
|
|
|
|
window.currentPage = page;
|
|
|
|
|
|
|
|
window.currentCategory = currentCategory;
|
|
|
|
|
|
|
|
sessionStorage.setItem('products_current_page', page);
|
|
|
|
|
|
|
|
console.log('Show load more button - current page:', currentPage);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
saveState();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (!append) {
|
|
|
|
|
|
|
|
grid.style.display = 'block';
|
|
|
|
|
|
|
|
if (initialLoader) {
|
|
|
|
|
|
|
|
initialLoader.style.display = 'none';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
grid.innerHTML = '<div class="col-span-full text-center py-12"><p class="text-gray-500 dark:text-gray-400">No products found.</p></div>';
|
|
|
|
|
|
|
|
updateItemCount(0);
|
|
|
|
|
|
|
|
if (loadMoreBtn) {
|
|
|
|
|
|
|
|
loadMoreBtn.style.display = 'none';
|
|
|
|
|
|
|
|
const loadMoreContainer = loadMoreBtn.parentElement;
|
|
|
|
|
|
|
|
if (loadMoreContainer) {
|
|
|
|
|
|
|
|
loadMoreContainer.style.display = 'none';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.catch(error => {
|
|
|
|
|
|
|
|
console.error('Error loading products:', error);
|
|
|
|
|
|
|
|
const initialLoader = document.getElementById('initial-loader');
|
|
|
|
|
|
|
|
if (!append) {
|
|
|
|
|
|
|
|
grid.style.display = 'block';
|
|
|
|
|
|
|
|
if (initialLoader) {
|
|
|
|
|
|
|
|
initialLoader.style.display = 'none';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
grid.innerHTML = '<div class="col-span-full text-center py-12"><p class="text-red-500">Error loading products. Please try again.</p></div>';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (loadMoreBtn && append) {
|
|
|
|
|
|
|
|
loadMoreBtn.innerHTML = originalLoadMoreBtnContent;
|
|
|
|
|
|
|
|
loadMoreBtn.disabled = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function filterSelection(category, clickedBtn) {
|
|
|
|
|
|
|
|
currentCategory = category;
|
|
|
|
|
|
|
|
window.currentCategory = category;
|
|
|
|
|
|
|
|
updateButtonStyles(clickedBtn);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const grid = document.getElementById('product-grid');
|
|
|
|
|
|
|
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (loadMoreBtn) {
|
|
|
|
|
|
|
|
const loadMoreContainer = loadMoreBtn.parentElement;
|
|
|
|
|
|
|
|
if (loadMoreContainer) {
|
|
|
|
|
|
|
|
loadMoreContainer.style.display = 'flex';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loadProducts(category, 1, false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
|
|
|
const initialLoader = document.getElementById('initial-loader');
|
|
|
|
|
|
|
|
const grid = document.getElementById('product-grid');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
window.currentPage = 1;
|
|
|
|
|
|
|
|
window.currentCategory = 'all';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (initialLoader && grid) {
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
initialLoader.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof AOS !== 'undefined') {
|
|
|
|
|
|
|
|
AOS.refresh();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof window.ImageLoader !== 'undefined' && typeof window.ImageLoader.scan === 'function') {
|
|
|
|
|
|
|
|
window.ImageLoader.scan();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}, 500);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (loadMoreBtn) {
|
|
|
|
|
|
|
|
perPage = parseInt(loadMoreBtn.getAttribute('data-per-page')) || 6;
|
|
|
|
|
|
|
|
originalLoadMoreBtnContent = loadMoreBtn.innerHTML;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loadMoreBtn.addEventListener('click', function() {
|
|
|
|
|
|
|
|
const nextPage = currentPage + 1;
|
|
|
|
|
|
|
|
console.log('[Load More Button] Clicked:', {
|
|
|
|
|
|
|
|
'currentPage (local)': currentPage,
|
|
|
|
|
|
|
|
'window.currentPage': window.currentPage,
|
|
|
|
|
|
|
|
'nextPage': nextPage,
|
|
|
|
|
|
|
|
'currentCategory': currentCategory,
|
|
|
|
|
|
|
|
'window.currentCategory': window.currentCategory
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
loadProducts(currentCategory, nextPage, true);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 页面显示事件处理
|
|
|
|
|
|
|
|
* 处理从产品详情页返回的情况
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
window.addEventListener('pageshow', function(event) {
|
|
|
|
|
|
|
|
console.log('[pageshow] Event triggered, persisted:', event.persisted);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有有效的状态需要恢复
|
|
|
|
|
|
|
|
if (ProductListStateManager.hasValidState()) {
|
|
|
|
|
|
|
|
console.log('[pageshow] Valid state found, restoring...');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复状态
|
|
|
|
|
|
|
|
const restored = ProductListStateManager.restore();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (restored) {
|
|
|
|
|
|
|
|
// 恢复滚动位置
|
|
|
|
|
|
|
|
ProductListStateManager.restoreScrollPosition();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 清除导航标记,避免重复恢复
|
|
|
|
|
|
|
|
sessionStorage.removeItem(ProductListStateManager.KEYS.NAVIGATED_FROM_LIST);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[pageshow] State restored successfully');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
console.log('[pageshow] No valid state to restore');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果不是从产品详情页返回,清除所有状态
|
|
|
|
|
|
|
|
console.log('[pageshow] Not navigated from list, clearing all states');
|
|
|
|
|
|
|
|
if (!ProductListStateManager.hasValidState()) {
|
|
|
|
|
|
|
|
ProductListStateManager.clear();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 滚动到顶部
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
window.scrollTo({ top: 0, behavior: 'auto' });
|
|
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const stickyEl = document.getElementById('filter-sticky-wrapper');
|
|
|
|
|
|
|
|
const filterBg = document.getElementById('filter-bg');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (stickyEl && filterBg) {
|
|
|
|
|
|
|
|
const sentinel = document.createElement('div');
|
|
|
|
|
|
|
|
sentinel.setAttribute('id', 'sticky-sentinel');
|
|
|
|
|
|
|
|
stickyEl.parentNode.insertBefore(sentinel, stickyEl);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
|
|
|
|
|
|
entries.forEach(entry => {
|
|
|
|
|
|
|
|
if (!entry.isIntersecting && entry.boundingClientRect.top < 0) {
|
|
|
|
|
|
|
|
filterBg.classList.add('border-gray-200', 'dark:border-gray-800', 'shadow-sm');
|
|
|
|
|
|
|
|
filterBg.classList.remove('border-transparent');
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
filterBg.classList.remove('border-gray-200', 'dark:border-gray-800', 'shadow-sm');
|
|
|
|
|
|
|
|
filterBg.classList.add('border-transparent');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}, {
|
|
|
|
|
|
|
|
threshold: 0,
|
|
|
|
|
|
|
|
rootMargin: '-72px 0px 0px 0px'
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
observer.observe(sentinel);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const navigatedFromList = sessionStorage.getItem('products_navigated_from_list');
|
|
|
|
|
|
|
|
const savedGridHtml = sessionStorage.getItem('products_grid_html');
|
|
|
|
|
|
|
|
const savedCategory = sessionStorage.getItem('products_current_category');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('DOMContentLoaded - navigatedFromList:', navigatedFromList, 'savedGridHtml:', !!savedGridHtml);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (navigatedFromList === 'true' && savedGridHtml) {
|
|
|
|
|
|
|
|
console.log('From product detail with saved HTML, skipping DOMContentLoaded initialization');
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (navigatedFromList !== 'true' && !savedGridHtml) {
|
|
|
|
|
|
|
|
console.log('Normal page load - setting default button and clearing cache');
|
|
|
|
|
|
|
|
updateButtonStyles(document.querySelector('[data-filter="all"]'));
|
|
|
|
|
|
|
|
sessionStorage.clear();
|
|
|
|
|
|
|
|
} else if (savedCategory) {
|
|
|
|
|
|
|
|
console.log('Restoring category button:', savedCategory);
|
|
|
|
|
|
|
|
const activeBtn = document.querySelector(`[data-filter="${savedCategory}"]`);
|
|
|
|
|
|
|
|
if (activeBtn) {
|
|
|
|
|
|
|
|
updateButtonStyles(activeBtn);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!savedGridHtml) {
|
|
|
|
|
|
|
|
updateItemCount(document.querySelectorAll('.product-card').length);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('click', function(event) {
|
|
|
|
|
|
|
|
const target = event.target;
|
|
|
|
|
|
|
|
console.log('[Click Event] Target:', target.tagName, target.className);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const link = target.closest('.product-detail-link');
|
|
|
|
|
|
|
|
if (link) {
|
|
|
|
|
|
|
|
console.log('[Click Event] Product detail link clicked:', link.href);
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[Click Event] Saving state before navigation...');
|
|
|
|
|
|
|
|
const saved = ProductListStateManager.save();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[Click Event] State saved, navigating to:', link.href);
|
|
|
|
|
|
|
|
window.location.href = link.href;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}, true);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
</script>
|