Merge pull request 'feat(shop): 商品详情/归档壳层 1:1 同家族卡片与内容流' (#74) from worktree-product-shell-1to1 into main

Reviewed-on: CYL/wordpress-theme-aksrt#74
This commit is contained in:
2026-07-12 12:01:06 +08:00
6 changed files with 152 additions and 113 deletions
+26
View File
@@ -463,3 +463,29 @@ html.dark .aksrt-product-page__content img {
font-size: 13px;
color: var(--color-ink-3);
}
/* Product detail shell — align with single content stream */
.content-shell--product .content-wrap {
padding-top: 8px;
}
.content-shell--product .aksrt-product-page {
width: 100%;
}
.aksrt-product-page__related {
margin-top: 28px;
padding-top: 8px;
}
.aksrt-product-page__related-title {
margin: 0 0 14px;
font-size: 18px;
font-weight: 700;
color: var(--color-ink, #1f2937);
}
.aksrt-product-page__related-grid.aksrt-product-archive__grid {
margin: 0;
}
.aksrt-product-page__merchant-link {
display: inline-block;
margin-top: 4px;
}
+41 -18
View File
@@ -37,12 +37,15 @@ $author_id = (int) get_post_field('post_author', $product_id);
$author = $author_id > 0 ? get_userdata($author_id) : null;
?>
<main id="main-content" class="site-main content-shell" tabindex="-1">
<main id="main-content" class="site-main content-shell content-shell--stream content-shell--product" tabindex="-1">
<?php if (function_exists('aksrt_breadcrumbs')) { aksrt_breadcrumbs(); } ?>
<?php if (!empty($product)) : ?>
<div class="content-wrap container">
<div class="content-layout content-layout--no-sidebar">
<div class="content-layout__primary">
<div
class="aksrt-product-page container"
class="aksrt-product-page"
data-aksrt-product-page
data-product-id="<?php echo esc_attr((string) $product_id); ?>"
data-options='<?php echo esc_attr(wp_json_encode($options, JSON_UNESCAPED_UNICODE)); ?>'
@@ -236,12 +239,16 @@ $author = $author_id > 0 ? get_userdata($author_id) : null;
</div>
<!-- Merchant info -->
<?php if ($author) : ?>
<?php if ($author) :
$store_url = function_exists('aksrt_get_user_home_url')
? add_query_arg('tab', 'product', aksrt_get_user_home_url($author_id))
: get_author_posts_url($author_id);
?>
<div class="aksrt-product-page__merchant">
<img src="<?php echo esc_url(function_exists('aksrt_get_avatar_url') ? aksrt_get_avatar_url($author_id) : get_avatar_url($author_id)); ?>" alt="" class="aksrt-product-page__merchant-avatar">
<div>
<strong><?php echo esc_html($author->display_name); ?></strong>
<a href="<?php echo esc_url(get_author_posts_url($author_id)); ?>?tab=products" class="aksrt-product-page__merchant-link"><?php esc_html_e('进入店铺', 'aksrt'); ?></a>
<a href="<?php echo esc_url($store_url); ?>" class="aksrt-product-page__merchant-link"><?php esc_html_e('进入店铺', 'aksrt'); ?></a>
</div>
</div>
<?php endif; ?>
@@ -312,11 +319,17 @@ $author = $author_id > 0 ? get_userdata($author_id) : null;
<!-- Related products -->
<?php
$related_query = new WP_Query([
$related_items = [];
if ($shop && method_exists($shop, 'build_product_card_view')) {
$related_q = new WP_Query([
'post_type' => 'aksrt_product',
'posts_per_page' => 6,
'post_status' => 'publish',
'post__not_in' => [$product_id],
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'tax_query' => !empty($product['categories']) ? [[
'taxonomy' => 'aksrt_product_cat',
'field' => 'term_id',
@@ -324,21 +337,26 @@ $author = $author_id > 0 ? get_userdata($author_id) : null;
]] : [],
'orderby' => 'rand',
]);
while ($related_q->have_posts()) {
$related_q->the_post();
$card = $shop->build_product_card_view((int) get_the_ID());
if (!empty($card['id'])) {
$related_items[] = $card;
}
}
wp_reset_postdata();
}
?>
<?php if ($related_query->have_posts()) : ?>
<?php if (!empty($related_items)) : ?>
<section class="aksrt-product-page__related">
<h2><?php esc_html_e('相关推荐', 'aksrt'); ?></h2>
<div class="aksrt-product-page__related-grid">
<?php while ($related_query->have_posts()) : $related_query->the_post(); ?>
<a href="<?php echo esc_url(get_permalink()); ?>" class="aksrt-product-page__related-card">
<?php $thumb = $shop ? $shop->resolve_main_image(get_the_ID()) : get_the_post_thumbnail_url(get_the_ID(), 'medium'); ?>
<?php if ($thumb) : ?>
<img src="<?php echo esc_url($thumb); ?>" alt="<?php the_title_attribute(); ?>" loading="lazy">
<?php endif; ?>
<h3><?php the_title(); ?></h3>
<strong>¥<?php echo esc_html(number_format((float) get_post_meta(get_the_ID(), '_aksrt_price', true), 2)); ?></strong>
</a>
<?php endwhile; wp_reset_postdata(); ?>
<h2 class="aksrt-product-page__related-title"><?php esc_html_e('相关推荐', 'aksrt'); ?></h2>
<div class="aksrt-product-archive__grid aksrt-product-archive__grid--default aksrt-product-page__related-grid">
<?php foreach ($related_items as $related_product) :
get_template_part('templates/parts/shop/product', 'card', [
'product' => $related_product,
'show_sales' => ((int) ($related_product['sales'] ?? 0) > 0),
]);
endforeach; ?>
</div>
</section>
<?php endif; ?>
@@ -397,6 +415,11 @@ $author = $author_id > 0 ? get_userdata($author_id) : null;
</div>
<?php endif; ?>
</div><!-- .aksrt-product-page -->
</div><!-- .content-layout__primary -->
</div><!-- .content-layout -->
</div><!-- .content-wrap -->
<!-- Mobile purchase tabbar -->
<div class="aksrt-product-page__mobile-bar" data-aksrt-mobile-purchase>
<button type="button" class="aksrt-product-page__mobile-btn" data-aksrt-toggle-fav="<?php echo esc_attr((string) $product_id); ?>">
+59
View File
@@ -0,0 +1,59 @@
<?php
/**
* Shared product card (archive / related / storefront).
*
* Expected args:
* - product: array from AKSRT_Shop::build_product_card_view()
* - show_sales: bool (optional)
* - sales_cfg: array (optional; used when show_sales not provided)
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
$args = is_array($args ?? null) ? $args : [];
$product = is_array($args['product'] ?? null) ? $args['product'] : [];
if (empty($product) || empty($product['id'])) {
return;
}
$sales = (int) ($product['sales'] ?? 0);
if (array_key_exists('show_sales', $args)) {
$show_sales = !empty($args['show_sales']);
} else {
$sales_cfg = is_array($args['sales_cfg'] ?? null) ? $args['sales_cfg'] : [];
$mode = (string) ($sales_cfg['show'] ?? 'show');
$min_sales = max(0, (int) ($sales_cfg['show_min'] ?? 0));
$show_sales = ($mode === 'show' && $sales > 0) || ($mode === 'threshold' && $sales >= $min_sales);
}
$title = (string) ($product['title'] ?? '');
$permalink = (string) ($product['permalink'] ?? '#');
$image = (string) ($product['image'] ?? '');
$excerpt = (string) ($product['excerpt'] ?? '');
$price = (float) ($product['price'] ?? 0);
?>
<article class="aksrt-product-card">
<a href="<?php echo esc_url($permalink !== '' ? $permalink : '#'); ?>" class="aksrt-product-card__link">
<?php if ($image !== '') : ?>
<div class="aksrt-product-card__image">
<img src="<?php echo esc_url($image); ?>" alt="<?php echo esc_attr($title); ?>" loading="lazy">
</div>
<?php else : ?>
<div class="aksrt-product-card__image aksrt-product-card__image--placeholder"><i class="fas fa-image" aria-hidden="true"></i></div>
<?php endif; ?>
<div class="aksrt-product-card__body">
<h3 class="aksrt-product-card__title"><?php echo esc_html($title); ?></h3>
<?php if ($excerpt !== '') : ?>
<p class="aksrt-product-card__excerpt"><?php echo esc_html(wp_trim_words($excerpt, 15)); ?></p>
<?php endif; ?>
<div class="aksrt-product-card__footer">
<span class="aksrt-product-card__price">¥<?php echo esc_html(number_format($price, 2)); ?></span>
<?php if ($show_sales) : ?>
<span class="aksrt-product-card__sales"><?php printf(esc_html__('已售 %d', 'aksrt'), $sales); ?></span>
<?php endif; ?>
</div>
</div>
</a>
</article>
+5 -26
View File
@@ -145,32 +145,11 @@ extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ext
?>
<div class="aksrt-product-archive__grid aksrt-product-archive__grid--default uh-product-grid">
<?php foreach ($product_items as $product) :
$sales = (int) ($product['sales'] ?? 0);
?>
<article class="aksrt-product-card">
<a href="<?php echo esc_url((string) ($product['permalink'] ?? '#')); ?>" class="aksrt-product-card__link">
<?php if (!empty($product['image'])) : ?>
<div class="aksrt-product-card__image">
<img src="<?php echo esc_url((string) $product['image']); ?>" alt="<?php echo esc_attr((string) ($product['title'] ?? '')); ?>" loading="lazy">
</div>
<?php else : ?>
<div class="aksrt-product-card__image aksrt-product-card__image--placeholder"><i class="fas fa-image"></i></div>
<?php endif; ?>
<div class="aksrt-product-card__body">
<h3 class="aksrt-product-card__title"><?php echo esc_html((string) ($product['title'] ?? '')); ?></h3>
<?php if (!empty($product['excerpt'])) : ?>
<p class="aksrt-product-card__excerpt"><?php echo esc_html(wp_trim_words((string) $product['excerpt'], 15)); ?></p>
<?php endif; ?>
<div class="aksrt-product-card__footer">
<span class="aksrt-product-card__price">¥<?php echo esc_html(number_format((float) ($product['price'] ?? 0), 2)); ?></span>
<?php if ($sales > 0) : ?>
<span class="aksrt-product-card__sales"><?php printf(esc_html__('已售 %d', 'aksrt'), $sales); ?></span>
<?php endif; ?>
</div>
</div>
</a>
</article>
<?php endforeach; ?>
get_template_part('templates/parts/shop/product', 'card', [
'product' => $product,
'show_sales' => ((int) ($product['sales'] ?? 0) > 0),
]);
endforeach; ?>
</div>
<?php if ($product_max_pages > 1 && function_exists('aksrt_render_profile_pagination')) : ?>
<?php aksrt_render_profile_pagination($product_max_pages, $product_page, 'product_page'); ?>
+5 -29
View File
@@ -80,35 +80,11 @@ $order_options = [
<?php if (!empty($products)) : ?>
<div class="aksrt-product-archive__grid aksrt-product-archive__grid--<?php echo esc_attr($style); ?>" id="aksrt-product-grid" data-aksrt-product-grid>
<?php foreach ($products as $product) :
$sales = (int) ($product['sales'] ?? 0);
$mode = (string) ($sales_cfg['show'] ?? 'show');
$min_sales = max(0, (int) ($sales_cfg['show_min'] ?? 0));
$show_sales = ($mode === 'show' && $sales > 0) || ($mode === 'threshold' && $sales >= $min_sales);
?>
<article class="aksrt-product-card">
<a href="<?php echo esc_url((string) ($product['permalink'] ?? '#')); ?>" class="aksrt-product-card__link">
<?php if (!empty($product['image'])) : ?>
<div class="aksrt-product-card__image">
<img src="<?php echo esc_url((string) $product['image']); ?>" alt="<?php echo esc_attr((string) ($product['title'] ?? '')); ?>" loading="lazy">
</div>
<?php else : ?>
<div class="aksrt-product-card__image aksrt-product-card__image--placeholder"><i class="fas fa-image"></i></div>
<?php endif; ?>
<div class="aksrt-product-card__body">
<h3 class="aksrt-product-card__title"><?php echo esc_html((string) ($product['title'] ?? '')); ?></h3>
<?php if (!empty($product['excerpt'])) : ?>
<p class="aksrt-product-card__excerpt"><?php echo esc_html(wp_trim_words((string) $product['excerpt'], 15)); ?></p>
<?php endif; ?>
<div class="aksrt-product-card__footer">
<span class="aksrt-product-card__price">¥<?php echo esc_html(number_format((float) ($product['price'] ?? 0), 2)); ?></span>
<?php if ($show_sales) : ?>
<span class="aksrt-product-card__sales"><?php printf(esc_html__('已售 %d', 'aksrt'), $sales); ?></span>
<?php endif; ?>
</div>
</div>
</a>
</article>
<?php endforeach; ?>
get_template_part('templates/parts/shop/product', 'card', [
'product' => $product,
'sales_cfg' => $sales_cfg,
]);
endforeach; ?>
</div>
<?php if ($max_pages > 1) : ?>
+5 -29
View File
@@ -80,35 +80,11 @@ $order_options = [
<?php if (!empty($products)) : ?>
<div class="aksrt-product-archive__grid aksrt-product-archive__grid--<?php echo esc_attr($style); ?>" id="aksrt-product-grid" data-aksrt-product-grid>
<?php foreach ($products as $product) :
$sales = (int) ($product['sales'] ?? 0);
$mode = (string) ($sales_cfg['show'] ?? 'show');
$min_sales = max(0, (int) ($sales_cfg['show_min'] ?? 0));
$show_sales = ($mode === 'show' && $sales > 0) || ($mode === 'threshold' && $sales >= $min_sales);
?>
<article class="aksrt-product-card">
<a href="<?php echo esc_url((string) ($product['permalink'] ?? '#')); ?>" class="aksrt-product-card__link">
<?php if (!empty($product['image'])) : ?>
<div class="aksrt-product-card__image">
<img src="<?php echo esc_url((string) $product['image']); ?>" alt="<?php echo esc_attr((string) ($product['title'] ?? '')); ?>" loading="lazy">
</div>
<?php else : ?>
<div class="aksrt-product-card__image aksrt-product-card__image--placeholder"><i class="fas fa-image"></i></div>
<?php endif; ?>
<div class="aksrt-product-card__body">
<h3 class="aksrt-product-card__title"><?php echo esc_html((string) ($product['title'] ?? '')); ?></h3>
<?php if (!empty($product['excerpt'])) : ?>
<p class="aksrt-product-card__excerpt"><?php echo esc_html(wp_trim_words((string) $product['excerpt'], 15)); ?></p>
<?php endif; ?>
<div class="aksrt-product-card__footer">
<span class="aksrt-product-card__price">¥<?php echo esc_html(number_format((float) ($product['price'] ?? 0), 2)); ?></span>
<?php if ($show_sales) : ?>
<span class="aksrt-product-card__sales"><?php printf(esc_html__('已售 %d', 'aksrt'), $sales); ?></span>
<?php endif; ?>
</div>
</div>
</a>
</article>
<?php endforeach; ?>
get_template_part('templates/parts/shop/product', 'card', [
'product' => $product,
'sales_cfg' => $sales_cfg,
]);
endforeach; ?>
</div>
<?php if ($max_pages > 1) : ?>