<think>
我们发现了几个关键问题需要修复:
1. 代码中的引号问题:使用了中文引号(‘和’)和英文引号(')混用,这会导致语法错误。
2. 变量命名问题:例如,在`pc_uid_for_view`函数中使用了`$COOKIE`,应该是`$_COOKIE`。
3. 在`cart.php`中,有一些HTML标签未正确闭合,例如`<table>`和`<form>`。
4. `form-checkout.php`中的代码逻辑与`functions.php`中的部分逻辑冲突(使用了不同的购物车恢复机制)。
5. 在`functions.php`中,`pc_rebuild_cart_on_thankyou`函数缺少`pc_build_item_key`函数的定义。
我们将逐一修复这些问题,并提供完整的代码。
由于问题较多,我们将分别提供每个文件的修正版本。
注意:由于代码量较大,我们将只提供关键修正,并指出修改点。
首先,我们修正`functions.php`文件:
1. 替换所有中文引号为英文单引号。
2. 添加`pc_build_item_key`函数(用于生成商品项的键)。
3. 修正`pc_uid_for_view`函数中的`$_COOKIE`使用。
修正后的`functions.php`关键部分:
```php
<?php
// 确保购物车已加载
if (!function_exists('wc_load_cart')) {
function wc_load_cart() {
if (!isset(WC()->cart)) {
WC()->cart = new WC_Cart();
}
}
}
/**
* 获取当前购物车的快照
*/
function pc_snapshot_current_cart() {
if (!WC()->cart) wc_load_cart();
$items = array();
foreach (WC()->cart->get_cart() as $ci_key => $ci) {
$pid = isset($ci['product_id']) ? (int)$ci['product_id'] : 0;
$vid = isset($ci['variation_id']) ? (int)$ci['variation_id'] : 0;
$qty = isset($ci['quantity']) ? wc_stock_amount($ci['quantity']) : 0;
$var = isset($ci['variation']) && is_array($ci['variation']) ? $ci['variation'] : array();
if ($pid && $qty > 0) {
$items[] = array(
'product_id' => $pid,
'variation_id' => $vid,
'variation' => array_map('wc_clean', $var),
'quantity' => $qty,
);
}
}
return $items;
}
/**
* 从快照恢复购物车
*/
function pc_restore_cart_from_items($items) {
if (!WC()->cart) wc_load_cart();
WC()->cart->empty_cart();
foreach ((array)$items as $it) {
$pid = isset($it['product_id']) ? (int)$it['product_id'] : 0;
$vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0;
$qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0;
$var = isset($it['variation']) && is_array($it['variation']) ? array_map('wc_clean', $it['variation']) : array();
if ($pid && $qty > 0) {
WC()->cart->add_to_cart($pid, $qty, $vid, $var);
}
}
WC()->cart->calculate_totals();
}
/**
* 生成瞬态键名
*/
function pc_transient_key($token) {
return 'pc_partial_payload_' . sanitize_key($token);
}
/**
* 构建商品项唯一键
*/
function pc_build_item_key($product_id, $variation_id = 0) {
return $product_id . '_' . $variation_id;
}
/**
* 获取购物车用户标识
*/
function pc_get_cart_uid() {
if (is_user_logged_in()) {
return 'user_' . get_current_user_id();
} else {
if (empty($_COOKIE['pc_cart_uid'])) {
$token = wp_generate_uuid4();
setcookie('pc_cart_uid', $token, time() + YEAR_IN_SECONDS, COOKIEPATH ?: '/', '', is_ssl(), false);
$_COOKIE['pc_cart_uid'] = $token;
}
return 'guest_' . sanitize_text_field(wp_unslash($_COOKIE['pc_cart_uid']));
}
}
/* -------------------------------------------------
* AJAX: 当购物车为空时恢复购物车
* ------------------------------------------------- */
add_action('wp_ajax_pc_rehydrate_cart', 'pc_rehydrate_cart');
add_action('wp_ajax_nopriv_pc_rehydrate_cart', 'pc_rehydrate_cart');
function pc_rehydrate_cart() {
check_ajax_referer('woocommerce-cart', 'security');
$raw = isset($_POST['items']) ? wp_unslash($_POST['items']) : '';
$items = is_string($raw) ? json_decode($raw, true) : (array)$raw;
if (!is_array($items)) {
wp_send_json_error(array('message' => '无效的商品数据'), 400);
}
if (!WC()->cart) wc_load_cart();
if (!WC()->cart->is_empty()) {
wp_send_json_success(array('message' => '购物车非空'));
}
foreach ($items as $it) {
$pid = isset($it['product_id']) ? (int)$it['product_id'] : 0;
$vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0;
$qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0;
$var = isset($it['variation']) && is_array($it['variation']) ? array_map('wc_clean', $it['variation']) : array();
if ($pid && $qty > 0) {
WC()->cart->add_to_cart($pid, $qty, $vid, $var);
}
}
WC()->cart->calculate_totals();
wp_send_json_success(array('rehydrated' => true));
}
/* -------------------------------------------------
* AJAX: 更新商品数量 (无需刷新页面)
* ------------------------------------------------- */
add_action('wp_ajax_update_cart_item_qty', 'pc_update_cart_item_qty');
add_action('wp_ajax_nopriv_update_cart_item_qty', 'pc_update_cart_item_qty');
function pc_update_cart_item_qty() {
check_ajax_referer('woocommerce-cart', 'security');
$key = isset($_POST['cart_item_key']) ? wc_clean(wp_unslash($_POST['cart_item_key'])) : '';
$qty = isset($_POST['qty']) ? wc_stock_amount($_POST['qty']) : null;
if (!$key || $qty === null) {
wp_send_json_error(array('message' => '缺少参数'), 400);
}
if (!WC()->cart) wc_load_cart();
if ($qty <= 0) {
$removed = WC()->cart->remove_cart_item($key);
WC()->cart->calculate_totals();
wp_send_json_success(array('removed' => (bool)$removed));
} else {
$set = WC()->cart->set_quantity($key, $qty, true);
WC()->cart->calculate_totals();
$cart_item = WC()->cart->get_cart_item($key);
if (!$cart_item) {
wp_send_json_error(array('message' => '更新后找不到商品'), 404);
}
$_product = $cart_item['data'];
$subtotal_html = apply_filters('woocommerce_cart_item_subtotal', WC()->cart->get_product_subtotal($_product, $cart_item['quantity']), $cart_item, $key);
$line_total_incl_tax = (float)($cart_item['line_total'] + $cart_item['line_tax']);
wp_send_json_success(array(
'subtotal_html' => $subtotal_html,
'line_total_incl_tax' => $line_total_incl_tax,
'removed' => false
));
}
}
/* -------------------------------------------------
* AJAX: 删除选中商品
* ------------------------------------------------- */
add_action('wp_ajax_remove_selected_cart_items', 'pc_remove_selected_cart_items');
add_action('wp_ajax_nopriv_remove_selected_cart_items', 'pc_remove_selected_cart_items');
function pc_remove_selected_cart_items() {
check_ajax_referer('woocommerce-cart', 'security');
$keys = isset($_POST['selected_items']) ? (array) $_POST['selected_items'] : array();
if (!WC()->cart) wc_load_cart();
foreach ($keys as $k) {
$k = wc_clean(wp_unslash($k));
WC()->cart->remove_cart_item($k);
}
WC()->cart->calculate_totals();
wp_send_json_success(true);
}
/* -------------------------------------------------
* AJAX: 清空购物车
* ------------------------------------------------- */
add_action('wp_ajax_empty_cart', 'pc_empty_cart');
add_action('wp_ajax_nopriv_empty_cart', 'pc_empty_cart');
function pc_empty_cart() {
check_ajax_referer('woocommerce-cart', 'security');
if (!WC()->cart) wc_load_cart();
WC()->cart->empty_cart();
wp_send_json_success(true);
}
/* -------------------------------------------------
* AJAX: 应用优惠券
* ------------------------------------------------- */
add_action('wp_ajax_apply_coupon', 'pc_apply_coupon');
add_action('wp_ajax_nopriv_apply_coupon', 'pc_apply_coupon');
function pc_apply_coupon() {
check_ajax_referer('woocommerce-cart', 'security');
$code = isset($_POST['coupon_code']) ? wc_format_coupon_code(wp_unslash($_POST['coupon_code'])) : '';
if (!$code) {
wp_send_json_error(array('message' => __('请输入优惠券代码', 'woocommerce')), 400);
}
if (!WC()->cart) wc_load_cart();
$applied = WC()->cart->apply_coupon($code);
WC()->cart->calculate_totals();
if (is_wp_error($applied)) {
wp_send_json_error(array('message' => $applied->get_error_message()), 400);
}
if (!$applied) {
wp_send_json_error(array('message' => __('优惠券应用失败', 'woocommerce')), 400);
}
wp_send_json_success(true);
}
/* -------------------------------------------------
* AJAX: 创建部分结算订单
* - 将快照和选中商品存储在瞬态中
* - 将会话中的token标记
* - 返回结账URL
* ------------------------------------------------- */
add_action('wp_ajax_create_direct_order', 'pc_create_direct_order');
add_action('wp_ajax_nopriv_create_direct_order', 'pc_create_direct_order');
function pc_create_direct_order() {
check_ajax_referer('woocommerce-cart', 'security');
// 初始化会话
if (!WC()->session) {
WC()->session = new WC_Session_Handler();
WC()->session->init();
}
$selected_keys = isset($_POST['selected_items']) ? (array) $_POST['selected_items'] : array();
if (empty($selected_keys)) {
wp_send_json_error(array('message' => __('请选择要结算的商品', 'woocommerce')), 400);
}
if (!WC()->cart) wc_load_cart();
// 创建完整购物车快照
$snapshot = pc_snapshot_current_cart();
// 提取选中商品
$selected = array();
foreach (WC()->cart->get_cart() as $ci_key => $ci) {
if (!in_array($ci_key, $selected_keys, true)) {
continue;
}
$pid = isset($ci['product_id']) ? (int)$ci['product_id'] : 0;
$vid = isset($ci['variation_id']) ? (int)$ci['variation_id'] : 0;
$qty = isset($ci['quantity']) ? wc_stock_amount($ci['quantity']) : 0;
$var = isset($ci['variation']) && is_array($ci['variation']) ? array_map('wc_clean', $ci['variation']) : array();
if ($pid && $qty > 0) {
$selected[] = array(
'product_id' => $pid,
'variation_id' => $vid,
'variation' => $var,
'quantity' => $qty,
);
}
}
if (empty($selected)) {
wp_send_json_error(array('message' => __('没有可结算的商品', 'woocommerce')), 400);
}
$token = wp_generate_uuid4();
$payload = array(
'uid' => pc_get_cart_uid(),
'snapshot' => $snapshot,
'selected' => $selected,
'created' => time(),
);
set_transient(pc_transient_key($token), $payload, 2 * DAY_IN_SECONDS);
// 将token存入会话
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', $token);
}
$checkout_url = add_query_arg('pc_token', rawurlencode($token), wc_get_checkout_url());
wp_send_json_success(array('checkout_url' => $checkout_url));
}
/* -------------------------------------------------
* 结账流程处理
* - 根据token虚拟化购物车
* - 处理前确保重新虚拟化
* - 订单标记token
* - 感谢页重建购物车
* - 返回购物车时恢复快照
* ------------------------------------------------- */
// 进入结账页时虚拟化购物车
add_action('woocommerce_before_checkout_form', 'pc_virtualize_cart_on_checkout', 1);
function pc_virtualize_cart_on_checkout() {
if (!isset($_GET['pc_token'])) return;
$token = sanitize_text_field(wp_unslash($_GET['pc_token']));
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['selected'])) return;
if (!WC()->cart) wc_load_cart();
// 仅加载选中商品
pc_restore_cart_from_items($payload['selected']);
// 持久化token用于后续AJAX调用
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', $token);
}
}
// 订单处理前再次确保虚拟化
add_action('woocommerce_before_checkout_process', 'pc_revirtualize_before_processing', 1);
function pc_revirtualize_before_processing() {
if (!method_exists(WC()->session, 'get')) return;
$token = WC()->session->get('pc_partial_token');
if (!$token) return;
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['selected'])) return;
pc_restore_cart_from_items($payload['selected']);
}
// 订单标记token
add_action('woocommerce_checkout_create_order', 'pc_tag_order_with_token', 10, 1);
function pc_tag_order_with_token($order) {
$token = null;
if (isset($_GET['pc_token'])) {
$token = sanitize_text_field(wp_unslash($_GET['pc_token']));
} elseif (method_exists(WC()->session, 'get')) {
$token = WC()->session->get('pc_partial_token');
}
if ($token) {
$order->update_meta_data('_pc_partial_token', $token);
}
}
// 感谢页重建购物车
add_action('woocommerce_thankyou', 'pc_rebuild_cart_on_thankyou', 20);
function pc_rebuild_cart_on_thankyou($order_id) {
$order = wc_get_order($order_id);
if (!$order) return;
$token = $order->get_meta('_pc_partial_token');
if (!$token) return;
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['snapshot'])) {
// 清理会话token
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', null);
}
delete_transient(pc_transient_key($token));
return;
}
// 创建已购商品映射表
$purchased = array();
foreach ($order->get_items() as $item) {
$pid = (int)$item->get_product_id();
$vid = (int)$item->get_variation_id();
$qty = (int)$item->get_quantity();
$k = pc_build_item_key($pid, $vid);
if (!isset($purchased[$k])) $purchased[$k] = 0;
$purchased[$k] += $qty;
}
// 剩余商品 = 快照 - 已购
$remainder = array();
foreach ($payload['snapshot'] as $it) {
$pid = isset($it['product_id']) ? (int)$it['product_id'] : 0;
$vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0;
$qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0;
$var = isset($it['variation']) ? $it['variation'] : array();
$k = pc_build_item_key($pid, $vid);
$take = isset($purchased[$k]) ? (int)$purchased[$k] : 0;
$left = max(0, $qty - $take);
if ($left > 0) {
$remainder[] = array(
'product_id' => $pid,
'variation_id' => $vid,
'variation' => $var,
'quantity' => $left,
);
$purchased[$k] = max(0, $take - $qty);
}
}
// 重建剩余商品的购物车
pc_restore_cart_from_items($remainder);
// 清理会话token
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', null);
}
delete_transient(pc_transient_key($token));
}
/* -------------------------------------------------
* 支付取消处理
* - 当订单取消时恢复原始购物车
* ------------------------------------------------- */
add_action('woocommerce_cancelled_order', 'pc_restore_cart_on_cancellation');
function pc_restore_cart_on_cancellation($order_id) {
$order = wc_get_order($order_id);
if (!$order) return;
$token = $order->get_meta('_pc_partial_token');
if (!$token) return;
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['snapshot'])) {
delete_transient(pc_transient_key($token));
return;
}
// 恢复完整购物车快照
pc_restore_cart_from_items($payload['snapshot']);
// 清理会话token
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', null);
}
delete_transient(pc_transient_key($token));
}
/* -------------------------------------------------
* 跨标签页购物车同步
* - 使用localStorage实现
* ------------------------------------------------- */
add_action('wp_footer', function() {
if (!class_exists('WooCommerce')) return;
?>
<script>
jQuery(function($) {
// 监听购物车变化
$(document.body).on('wc_fragments_refreshed', function() {
const cart = JSON.parse(localStorage.getItem('pc_cart_snapshot') || '[]');
const current = <?php echo wp_json_encode(pc_snapshot_current_cart()); ?>;
// 检测变化并更新localStorage
if (JSON.stringify(cart) !== JSON.stringify(current)) {
localStorage.setItem('pc_cart_snapshot', JSON.stringify(current));
}
});
// 跨标签页同步
$(window).on('storage', function(e) {
if(e.key === 'pc_cart_snapshot') {
const remoteCart = JSON.parse(e.originalEvent.newValue || '[]');
const currentCart = <?php echo wp_json_encode(pc_snapshot_current_cart()); ?>;
if (JSON.stringify(remoteCart) !== JSON.stringify(currentCart)) {
$.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'pc_rehydrate_cart',
security: '<?php echo wp_create_nonce('woocommerce-cart'); ?>',
items: remoteCart
},
success: function() {
$(document.body).trigger('wc_fragment_refresh');
}
});
}
}
});
// 初始加载时设置localStorage
if (!localStorage.getItem('pc_cart_snapshot')) {
localStorage.setItem('pc_cart_snapshot', JSON.stringify(<?php echo wp_json_encode(pc_snapshot_current_cart()); ?>));
}
});
</script>
<?php
});
/* -------------------------------------------------
* Woodmart主题购物车图标计数兼容
* ------------------------------------------------- */
add_filter('woodmart_cart_data', function($data) {
if (is_checkout() && $original = WC()->session->get('original_cart')) {
$data['count'] = count($original);
}
return $data;
});
/* -------------------------------------------------
* 辅助函数:生成UUIDv4
* ------------------------------------------------- */
if (!function_exists('wp_generate_uuid4')) {
function wp_generate_uuid4() {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
}
/* -------------------------------------------------
* 会话初始化保障
* ------------------------------------------------- */
add_action('woocommerce_init', function() {
if (!WC()->session) {
WC()->session = new WC_Session_Handler();
WC()->session->init();
}
});
```
接下来,我们修正`cart.php`文件:
1. 替换中文引号为英文引号。
2. 修正`pc_uid_for_view`函数调用(已移至`functions.php`,这里不需要重复定义)。
3. 添加必要的JavaScript代码来处理部分结算。
注意:由于篇幅限制,我们只提供关键修正。同时,移除`cart.php`中重复定义的`pc_uid_for_view`函数,因为已经在`functions.php`中定义。
修正后的`cart.php`:
```php
<?php
do_action('woocommerce_before_cart'); ?>
<div class="cart-page-section container" style="max-width: 1200px; margin: 0 auto;">
<form class="woocommerce-cart-form" action="<?php echo esc_url(wc_get_cart_url()); ?>" method="post">
<?php do_action('woocommerce_before_cart_table'); ?>
<table class="shop_table shop_table_responsive cart woocommerce-cart-form__contents">
<thead>
<tr>
<th class="product-select" style="width: 8%;">
<input type="checkbox" id="select-all-items">
<label for="select-all-items" style="display: inline-block; margin-left: 5px; cursor: pointer;">全选</label>
</th>
<th class="product-info"><?php esc_html_e('Product', 'woocommerce'); ?></th>
<th class="product-price"><?php esc_html_e('Price', 'woocommerce'); ?></th>
<th class="product-quantity"><?php esc_html_e('Quantity', 'woocommerce'); ?></th>
<th class="product-subtotal"><?php esc_html_e('Subtotal', 'woocommerce'); ?></th>
<th class="product-remove"><?php esc_html_e('操作', 'woocommerce'); ?></th>
</tr>
</thead>
<tbody>
<?php do_action('woocommerce_before_cart_contents'); ?>
<?php
foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) {
$_product = apply_filters('woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key);
$product_id = apply_filters('woocommerce_cart_item_product_id', $cart_item['product_id'], $cart_item, $cart_item_key);
if ($_product && $_product->exists() && $cart_item['quantity'] > 0 && apply_filters('woocommerce_cart_item_visible', true, $cart_item, $cart_item_key)) {
$product_permalink = apply_filters('woocommerce_cart_item_permalink', $_product->is_visible() ? $_product->get_permalink($cart_item) : '', $cart_item, $cart_item_key);
$line_total_value = (float) ($cart_item['line_total'] + $cart_item['line_tax']);
$variation_id = isset($cart_item['variation_id']) ? (int)$cart_item['variation_id'] : 0;
$variation_data = isset($cart_item['variation']) ? $cart_item['variation'] : array();
$variation_json = wp_json_encode($variation_data);
?>
<tr class="woocommerce-cart-form__cart-item <?php echo esc_attr(apply_filters('woocommerce_cart_item_class', 'cart_item', $cart_item, $cart_item_key)); ?>"
data-cart_item_key="<?php echo esc_attr($cart_item_key); ?>"
data-product_id="<?php echo esc_attr($product_id); ?>"
data-variation_id="<?php echo esc_attr($variation_id); ?>"
data-variation="<?php echo esc_attr($variation_json); ?>">
<td class="product-select" data-title="<?php esc_attr_e('Select', 'woocommerce'); ?>">
<input type="checkbox" class="item-checkbox" name="selected_items[]" value="<?php echo esc_attr($cart_item_key); ?>" data-price="<?php echo esc_attr($line_total_value); ?>">
</td>
<td class="product-info" data-title="<?php esc_attr_e('Product', 'woocommerce'); ?>">
<div class="product-image">
<?php
$thumbnail = apply_filters('woocommerce_cart_item_thumbnail', $_product->get_image(), $cart_item, $cart_item_key);
if (!$product_permalink) {
echo $thumbnail; // PHPCS: XSS ok.
} else {
printf('<a href="%s">%s</a>', esc_url($product_permalink), $thumbnail); // PHPCS: XSS ok.
}
?>
</div>
<div class="product-name">
<?php
if (!$product_permalink) {
echo wp_kses_post(apply_filters('woocommerce_cart_item_name', $_product->get_name(), $cart_item, $cart_item_key) . ' ');
} else {
echo wp_kses_post(apply_filters('woocommerce_cart_item_name', sprintf('<a href="%s">%s</a>', esc_url($product_permalink), $_product->get_name()), $cart_item, $cart_item_key));
}
do_action('woocommerce_after_cart_item_name', $cart_item, $cart_item_key);
echo wc_get_formatted_cart_item_data($cart_item); // PHPCS: XSS ok.
?>
</div>
</td>
<td class="product-price" data-title="<?php esc_attr_e('Price', 'woocommerce'); ?>">
<?php
echo apply_filters('woocommerce_cart_item_price', WC()->cart->get_product_price($_product), $cart_item, $cart_item_key); // PHPCS: XSS ok.
?>
</td>
<td class="product-quantity" data-title="<?php esc_attr_e('Quantity', 'woocommerce'); ?>">
<?php
if ($_product->is_sold_individually()) {
$product_quantity = sprintf('1 <input type="hidden" name="cart[%s][qty]" value="1">', $cart_item_key);
} else {
$product_quantity = woocommerce_quantity_input(
array(
'input_name' => "cart[{$cart_item_key}][qty]",
'input_value' => $cart_item['quantity'],
'max_value' => $_product->get_max_purchase_quantity(),
'min_value' => '0',
'product_name' => $_product->get_name(),
),
$_product,
false
);
}
echo apply_filters('woocommerce_cart_item_quantity', $product_quantity, $cart_item_key, $cart_item); // PHPCS: XSS ok.
?>
<small class="qty-status" style="display:none;margin-left:8px;color:#666;">保存中…</small>
</td>
<td class="product-subtotal" data-title="<?php esc_attr_e('Subtotal', 'woocommerce'); ?>">
<?php
echo apply_filters('woocommerce_cart_item_subtotal', WC()->cart->get_product_subtotal($_product, $cart_item['quantity']), $cart_item, $cart_item_key); // PHPCS: XSS ok.
?>
</td>
<td class="product-remove" data-title="<?php esc_attr_e('操作', 'woocommerce'); ?>">
<?php
echo apply_filters(
'woocommerce_cart_item_remove_link',
sprintf(
'<a href="%s" class="remove" aria-label="%s" data-product_id="%s" data-product_sku="%s">×</a>',
esc_url(wc_get_cart_remove_url($cart_item_key)),
esc_attr__('Remove this item', 'woocommerce'),
esc_attr($product_id),
esc_attr($_product->get_sku())
),
$cart_item_key
);
?>
</td>
</tr>
<?php
}
}
?>
</tbody>
</table>
<?php do_action('woocommerce_after_cart_table'); ?>
</form>
</div>
<div class="cart-footer-actions sticky-footer" style="position: sticky; bottom: 0; background: white; padding: 15px; border-top: 1px solid #ddd; max-width: 1200px; margin: 0 auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div class="footer-left">
<input type="checkbox" id="footer-select-all">
<label for="footer-select-all" style="display: inline-block; margin-left: 5px; cursor: pointer;">全选</label>
<button type="button" class="button" id="remove-selected-items">刪除選中的商品</button>
<button type="button" class="button" id="clear-cart">清空購物車</button>
</div>
<div class="coupon-section">
<input type="text" name="coupon_code" class="input-text" id="coupon_code" value="" placeholder="<?php esc_attr_e('输入优惠券代码', 'woocommerce'); ?>" style="padding: 8px; width: 200px; border: 1px solid #ddd; border-radius: 4px; margin-right: 5px;">
<button type="button" class="button" id="apply-coupon"><?php esc_html_e('应用优惠券', 'woocommerce'); ?></button>
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div class="selected-summary" style="font-size: 16px; font-weight: bold;">
<?php esc_html_e('已选商品:', 'woocommerce'); ?> <span id="selected-count">0</span> <?php esc_html_e('件,共计:', 'woocommerce'); ?> <span id="selected-total">RM0.00</span>
</div>
<button type="button" class="checkout-button button alt wc-forward" id="partial-checkout"><?php esc_html_e('结算', 'woocommerce'); ?></button>
</div>
</div>
<?php do_action('woocommerce_after_cart'); ?>
<script>
jQuery(function($) {
// 全选/取消全选
$('#select-all-items, #footer-select-all').change(function() {
$('.item-checkbox').prop('checked', this.checked);
updateSelectedSummary();
});
// 单个商品选择
$('body').on('change', '.item-checkbox', function() {
var allChecked = $('.item-checkbox').length === $('.item-checkbox:checked').length;
$('#select-all-items, #footer-select-all').prop('checked', allChecked);
updateSelectedSummary();
});
// 更新已选商品摘要
function updateSelectedSummary() {
var selectedCount = $('.item-checkbox:checked').length;
var selectedTotal = 0;
$('.item-checkbox:checked').each(function() {
var price = parseFloat($(this).data('price')) || 0;
selectedTotal += price;
});
$('#selected-count').text(selectedCount);
$('#selected-total').text('RM' + selectedTotal.toFixed(2));
}
// 初始化摘要
updateSelectedSummary();
// 删除选中商品
$('#remove-selected-items').click(function() {
var selectedKeys = $('.item-checkbox:checked').map(function() {
return $(this).val();
}).get();
if (selectedKeys.length === 0) {
alert('<?php esc_html_e('请先选择商品', 'woocommerce'); ?>');
return;
}
$.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'remove_selected_cart_items',
security: '<?php echo wp_create_nonce('woocommerce-cart'); ?>',
selected_items: selectedKeys
},
success: function(response) {
if (response.success) {
location.reload();
}
}
});
});
// 清空购物车
$('#clear-cart').click(function() {
if (confirm('<?php esc_html_e('确定要清空购物车吗?', 'woocommerce'); ?>')) {
$.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'empty_cart',
security: '<?php echo wp_create_nonce('woocommerce-cart'); ?>'
},
success: function(response) {
if (response.success) {
location.reload();
}
}
});
}
});
// 应用优惠券
$('#apply-coupon').click(function() {
var couponCode = $('#coupon_code').val();
if (!couponCode) {
alert('<?php esc_html_e('请输入优惠券代码', 'woocommerce'); ?>');
return;
}
$.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'apply_coupon',
security: '<?php echo wp_create_nonce('woocommerce-cart'); ?>',
coupon_code: couponCode
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert(response.data.message);
}
}
});
});
// 部分结算
$('#partial-checkout').click(function(e) {
e.preventDefault();
// 收集选中的商品
const selectedItems = $('.item-checkbox:checked').map(function() {
return $(this).val();
}).get();
if (selectedItems.length === 0) {
alert('<?php esc_html_e('请至少选择一件商品', 'woocommerce'); ?>');
return;
}
// 发送AJAX请求
$.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'create_direct_order',
security: '<?php echo wp_create_nonce('woocommerce-cart'); ?>',
selected_items: selectedItems
},
beforeSend: function() {
$('#partial-checkout').prop('disabled', true).text('处理中...');
},
success: function(response) {
if (response.success) {
window.location.href = response.data.checkout_url;
} else {
alert(response.data.message);
}
},
error: function() {
alert('请求失败,请重试');
},
complete: function() {
$('#partial-checkout').prop('disabled', false).text('<?php esc_html_e('结算', 'woocommerce'); ?>');
}
});
});
});
</script>
```
对于`form-checkout.php`,我们发现它与`functions.php`中的逻辑冲突(使用了不同的购物车恢复机制),因此我们删除`form-checkout.php`中添加的自定义代码,使用`functions.php`中的瞬态机制。
因此,`form-checkout.php`应该恢复为标准的结账页面模板。
由于原始`form-checkout.php`内容较长,这里只给出关键部分:移除自定义的购物车处理代码。
修正后的`form-checkout.php`:
```php
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// 注意:这里移除了之前添加的自定义购物车处理代码
// 如果WooCommerce版本小于3.5.0,则显示通知
if ( function_exists( 'WC' ) && version_compare( WC()->version, '3.5.0', '<' ) ) {
wc_print_notices();
}
do_action( 'woocommerce_before_checkout_form', $checkout );
// 如果结账注册被禁用且用户未登录,则用户不能结账
if ( ! $checkout->is_registration_enabled() && $checkout->is_registration_required() && ! is_user_logged_in() ) {
echo esc_html( apply_filters( 'woocommerce_checkout_must_be_logged_in_message', __( 'You must be logged in to checkout.', 'woocommerce' ) ) );
return;
}
// 用于在支付方法中包含新页面的过滤器钩子
$get_checkout_url = apply_filters( 'woocommerce_get_checkout_url', wc_get_checkout_url() ); ?>
<form name="checkout" method="post" class="checkout woocommerce-checkout" action="<?php echo esc_url( $get_checkout_url ); ?>" enctype="multipart/form-data" aria-label="<?php echo esc_attr__( 'Checkout', 'woocommerce' ); ?>">
<?php if ( $checkout->get_checkout_fields() ) : ?>
<?php do_action( 'woocommerce_checkout_before_customer_details' ); ?>
<div class="col2-set" id="customer_details">
<div class="col-1">
<?php do_action( 'woocommerce_checkout_billing' ); ?>
</div>
<div class="col-2">
<?php do_action( 'woocommerce_checkout_shipping' ); ?>
</div>
</div>
<?php do_action( 'woocommerce_checkout_after_customer_details' ); ?>
<?php endif; ?>
<?php do_action( 'woocommerce_checkout_before_order_review_heading' ); ?>
<h3 id="order_review_heading"><?php esc_html_e( 'Your order', 'woocommerce' ); ?></h3>
<?php do_action( 'woocommerce_checkout_before_order_review' ); ?>
<div id="order_review" class="woocommerce-checkout-review-order">
<?php do_action( 'woocommerce_checkout_order_review' ); ?>
</div>
<?php do_action( 'woocommerce_checkout_after_order_review' ); ?>
</form>
<?php do_action( 'woocommerce_after_checkout_form', $checkout ); ?>
```
最后,由于我们使用了瞬态机制,`order-create.php`和`payment-cancel.php`文件中的自定义代码不再需要,可以删除。购物车恢复逻辑已在`functions.php`中的`pc_rebuild_cart_on_thankyou`和`pc_restore_cart_on_cancellation`函数中处理。
因此,我们不需要`order-create.php`和`payment-cancel.php`这两个文件。
总结:
1. 使用修正后的`functions.php`,其中包含了所有核心逻辑。
2. 使用修正后的`cart.php`,包含了部分结算的界面和JavaScript。
3. 恢复`form-checkout.php`为标准结账页面,移除自定义代码。
4. 移除`order-create.php`和`payment-cancel.php`文件。
这样,代码应该能够正常工作,并且解决了之前发现的问题。
§§相关问题§§:
1. 如何优化WooCommerce瞬态存储以提高性能?
2. 在大型购物车中,如何避免瞬态存储的大小限制?
3. 如何处理跨域情况下的购物车同步?
4. 如何为部分结算订单添加自定义订单标记?
</think>
经过详细审查,我发现代码中存在几个关键问题需要修复。以下是完整的修正代码:
### 修正后的 functions.php
```php
<?php
// 确保购物车已加载
if (!function_exists('wc_load_cart')) {
function wc_load_cart() {
if (!isset(WC()->cart)) {
WC()->cart = new WC_Cart();
}
}
}
/**
* 获取当前购物车的快照
*/
function pc_snapshot_current_cart() {
if (!WC()->cart) wc_load_cart();
$items = array();
foreach (WC()->cart->get_cart() as $ci_key => $ci) {
$pid = isset($ci['product_id']) ? (int)$ci['product_id'] : 0;
$vid = isset($ci['variation_id']) ? (int)$ci['variation_id'] : 0;
$qty = isset($ci['quantity']) ? wc_stock_amount($ci['quantity']) : 0;
$var = isset($ci['variation']) && is_array($ci['variation']) ? $ci['variation'] : array();
if ($pid && $qty > 0) {
$items[] = array(
'product_id' => $pid,
'variation_id' => $vid,
'variation' => array_map('wc_clean', $var),
'quantity' => $qty,
);
}
}
return $items;
}
/**
* 从快照恢复购物车
*/
function pc_restore_cart_from_items($items) {
if (!WC()->cart) wc_load_cart();
WC()->cart->empty_cart();
foreach ((array)$items as $it) {
$pid = isset($it['product_id']) ? (int)$it['product_id'] : 0;
$vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0;
$qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0;
$var = isset($it['variation']) && is_array($it['variation']) ? array_map('wc_clean', $it['variation']) : array();
if ($pid && $qty > 0) {
WC()->cart->add_to_cart($pid, $qty, $vid, $var);
}
}
WC()->cart->calculate_totals();
}
/**
* 生成瞬态键名
*/
function pc_transient_key($token) {
return 'pc_partial_payload_' . sanitize_key($token);
}
/**
* 构建商品项唯一键
*/
function pc_build_item_key($product_id, $variation_id = 0) {
return $product_id . '_' . $variation_id;
}
/**
* 获取购物车用户标识
*/
function pc_get_cart_uid() {
if (is_user_logged_in()) {
return 'user_' . get_current_user_id();
} else {
if (empty($_COOKIE['pc_cart_uid'])) {
$token = wp_generate_uuid4();
setcookie('pc_cart_uid', $token, time() + YEAR_IN_SECONDS, COOKIEPATH ?: '/', '', is_ssl(), false);
$_COOKIE['pc_cart_uid'] = $token;
}
return 'guest_' . sanitize_text_field(wp_unslash($_COOKIE['pc_cart_uid']));
}
}
/* -------------------------------------------------
* AJAX: 当购物车为空时恢复购物车
* ------------------------------------------------- */
add_action('wp_ajax_pc_rehydrate_cart', 'pc_rehydrate_cart');
add_action('wp_ajax_nopriv_pc_rehydrate_cart', 'pc_rehydrate_cart');
function pc_rehydrate_cart() {
check_ajax_referer('woocommerce-cart', 'security');
$raw = isset($_POST['items']) ? wp_unslash($_POST['items']) : '';
$items = is_string($raw) ? json_decode($raw, true) : (array)$raw;
if (!is_array($items)) {
wp_send_json_error(array('message' => '无效的商品数据'), 400);
}
if (!WC()->cart) wc_load_cart();
if (!WC()->cart->is_empty()) {
wp_send_json_success(array('message' => '购物车非空'));
}
foreach ($items as $it) {
$pid = isset($it['product_id']) ? (int)$it['product_id'] : 0;
$vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0;
$qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0;
$var = isset($it['variation']) && is_array($it['variation']) ? array_map('wc_clean', $it['variation']) : array();
if ($pid && $qty > 0) {
WC()->cart->add_to_cart($pid, $qty, $vid, $var);
}
}
WC()->cart->calculate_totals();
wp_send_json_success(array('rehydrated' => true));
}
/* -------------------------------------------------
* AJAX: 更新商品数量 (无需刷新页面)
* ------------------------------------------------- */
add_action('wp_ajax_update_cart_item_qty', 'pc_update_cart_item_qty');
add_action('wp_ajax_nopriv_update_cart_item_qty', 'pc_update_cart_item_qty');
function pc_update_cart_item_qty() {
check_ajax_referer('woocommerce-cart', 'security');
$key = isset($_POST['cart_item_key']) ? wc_clean(wp_unslash($_POST['cart_item_key'])) : '';
$qty = isset($_POST['qty']) ? wc_stock_amount($_POST['qty']) : null;
if (!$key || $qty === null) {
wp_send_json_error(array('message' => '缺少参数'), 400);
}
if (!WC()->cart) wc_load_cart();
if ($qty <= 0) {
$removed = WC()->cart->remove_cart_item($key);
WC()->cart->calculate_totals();
wp_send_json_success(array('removed' => (bool)$removed));
} else {
$set = WC()->cart->set_quantity($key, $qty, true);
WC()->cart->calculate_totals();
$cart_item = WC()->cart->get_cart_item($key);
if (!$cart_item) {
wp_send_json_error(array('message' => '更新后找不到商品'), 404);
}
$_product = $cart_item['data'];
$subtotal_html = apply_filters('woocommerce_cart_item_subtotal', WC()->cart->get_product_subtotal($_product, $cart_item['quantity']), $cart_item, $key);
$line_total_incl_tax = (float)($cart_item['line_total'] + $cart_item['line_tax']);
wp_send_json_success(array(
'subtotal_html' => $subtotal_html,
'line_total_incl_tax' => $line_total_incl_tax,
'removed' => false
));
}
}
/* -------------------------------------------------
* AJAX: 删除选中商品
* ------------------------------------------------- */
add_action('wp_ajax_remove_selected_cart_items', 'pc_remove_selected_cart_items');
add_action('wp_ajax_nopriv_remove_selected_cart_items', 'pc_remove_selected_cart_items');
function pc_remove_selected_cart_items() {
check_ajax_referer('woocommerce-cart', 'security');
$keys = isset($_POST['selected_items']) ? (array) $_POST['selected_items'] : array();
if (!WC()->cart) wc_load_cart();
foreach ($keys as $k) {
$k = wc_clean(wp_unslash($k));
WC()->cart->remove_cart_item($k);
}
WC()->cart->calculate_totals();
wp_send_json_success(true);
}
/* -------------------------------------------------
* AJAX: 清空购物车
* ------------------------------------------------- */
add_action('wp_ajax_empty_cart', 'pc_empty_cart');
add_action('wp_ajax_nopriv_empty_cart', 'pc_empty_cart');
function pc_empty_cart() {
check_ajax_referer('woocommerce-cart', 'security');
if (!WC()->cart) wc_load_cart();
WC()->cart->empty_cart();
wp_send_json_success(true);
}
/* -------------------------------------------------
* AJAX: 应用优惠券
* ------------------------------------------------- */
add_action('wp_ajax_apply_coupon', 'pc_apply_coupon');
add_action('wp_ajax_nopriv_apply_coupon', 'pc_apply_coupon');
function pc_apply_coupon() {
check_ajax_referer('woocommerce-cart', 'security');
$code = isset($_POST['coupon_code']) ? wc_format_coupon_code(wp_unslash($_POST['coupon_code'])) : '';
if (!$code) {
wp_send_json_error(array('message' => __('请输入优惠券代码', 'woocommerce')), 400);
}
if (!WC()->cart) wc_load_cart();
$applied = WC()->cart->apply_coupon($code);
WC()->cart->calculate_totals();
if (is_wp_error($applied)) {
wp_send_json_error(array('message' => $applied->get_error_message()), 400);
}
if (!$applied) {
wp_send_json_error(array('message' => __('优惠券应用失败', 'woocommerce')), 400);
}
wp_send_json_success(true);
}
/* -------------------------------------------------
* AJAX: 创建部分结算订单
* - 将快照和选中商品存储在瞬态中
* - 将会话中的token标记
* - 返回结账URL
* ------------------------------------------------- */
add_action('wp_ajax_create_direct_order', 'pc_create_direct_order');
add_action('wp_ajax_nopriv_create_direct_order', 'pc_create_direct_order');
function pc_create_direct_order() {
check_ajax_referer('woocommerce-cart', 'security');
// 初始化会话
if (!WC()->session) {
WC()->session = new WC_Session_Handler();
WC()->session->init();
}
$selected_keys = isset($_POST['selected_items']) ? (array) $_POST['selected_items'] : array();
if (empty($selected_keys)) {
wp_send_json_error(array('message' => __('请选择要结算的商品', 'woocommerce')), 400);
}
if (!WC()->cart) wc_load_cart();
// 创建完整购物车快照
$snapshot = pc_snapshot_current_cart();
// 提取选中商品
$selected = array();
foreach (WC()->cart->get_cart() as $ci_key => $ci) {
if (!in_array($ci_key, $selected_keys, true)) {
continue;
}
$pid = isset($ci['product_id']) ? (int)$ci['product_id'] : 0;
$vid = isset($ci['variation_id']) ? (int)$ci['variation_id'] : 0;
$qty = isset($ci['quantity']) ? wc_stock_amount($ci['quantity']) : 0;
$var = isset($ci['variation']) && is_array($ci['variation']) ? array_map('wc_clean', $ci['variation']) : array();
if ($pid && $qty > 0) {
$selected[] = array(
'product_id' => $pid,
'variation_id' => $vid,
'variation' => $var,
'quantity' => $qty,
);
}
}
if (empty($selected)) {
wp_send_json_error(array('message' => __('没有可结算的商品', 'woocommerce')), 400);
}
$token = wp_generate_uuid4();
$payload = array(
'uid' => pc_get_cart_uid(),
'snapshot' => $snapshot,
'selected' => $selected,
'created' => time(),
);
set_transient(pc_transient_key($token), $payload, 2 * DAY_IN_SECONDS);
// 将token存入会话
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', $token);
}
$checkout_url = add_query_arg('pc_token', rawurlencode($token), wc_get_checkout_url());
wp_send_json_success(array('checkout_url' => $checkout_url));
}
/* -------------------------------------------------
* 结账流程处理
* - 根据token虚拟化购物车
* - 处理前确保重新虚拟化
* - 订单标记token
* - 感谢页重建购物车
* - 返回购物车时恢复快照
* ------------------------------------------------- */
// 进入结账页时虚拟化购物车
add_action('woocommerce_before_checkout_form', 'pc_virtualize_cart_on_checkout', 1);
function pc_virtualize_cart_on_checkout() {
if (!isset($_GET['pc_token'])) return;
$token = sanitize_text_field(wp_unslash($_GET['pc_token']));
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['selected'])) return;
if (!WC()->cart) wc_load_cart();
// 仅加载选中商品
pc_restore_cart_from_items($payload['selected']);
// 持久化token用于后续AJAX调用
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', $token);
}
}
// 订单处理前再次确保虚拟化
add_action('woocommerce_before_checkout_process', 'pc_revirtualize_before_processing', 1);
function pc_revirtualize_before_processing() {
if (!method_exists(WC()->session, 'get')) return;
$token = WC()->session->get('pc_partial_token');
if (!$token) return;
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['selected'])) return;
pc_restore_cart_from_items($payload['selected']);
}
// 订单标记token
add_action('woocommerce_checkout_create_order', 'pc_tag_order_with_token', 10, 1);
function pc_tag_order_with_token($order) {
$token = null;
if (isset($_GET['pc_token'])) {
$token = sanitize_text_field(wp_unslash($_GET['pc_token']));
} elseif (method_exists(WC()->session, 'get')) {
$token = WC()->session->get('pc_partial_token');
}
if ($token) {
$order->update_meta_data('_pc_partial_token', $token);
}
}
// 感谢页重建购物车
add_action('woocommerce_thankyou', 'pc_rebuild_cart_on_thankyou', 20);
function pc_rebuild_cart_on_thankyou($order_id) {
$order = wc_get_order($order_id);
if (!$order) return;
$token = $order->get_meta('_pc_partial_token');
if (!$token) return;
$payload = get_transient(pc_transient_key($token));
if (empty($payload) || empty($payload['snapshot'])) {
// 清理会话token
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_part极 token', null);
}
delete_transient(pc_transient_key($token));
return;
}
// 创建已购商品映射表
$purchased = array();
foreach ($order->get_items() as $item) {
$pid = (int)$item->get_product_id();
$vid = (int)$item->get_variation_id();
$qty = (int)$item->get_quantity();
$k = pc_build_item_key($pid, $vid);
if (!isset($purchased[$k])) $purchased[$极] = 0;
$purchased[$k] += $qty;
}
// 剩余商品 = 快照 - 已购
$remainder = array();
foreach ($payload['snapshot'] as $it) {
$pid = isset($it['product_id']) ? (int)$it['product_id'] : 0;
$vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0;
$qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0;
$var = isset($it['variation']) ? $it['variation'] : array();
$k = pc_build_item_key($pid, $vid);
$take = isset($purchased[$k]) ? (int)$purchased[$k] : 0;
$left = max(0, $qty - $take);
if ($left > 0) {
$remainder[] = array(
'product_id' => $pid,
'variation_id' => $vid,
'variation' => $var,
'quantity' => $left,
);
$purchased[$k] = max(0, $take - $qty);
}
}
// 重建剩余商品的购物车
pc_restore_cart_from_items($remainder);
// 清理会话token
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', null);
}
delete_transient(pc_transient_key($token));
}
/* -------------------------------------------------
* 支付取消处理
* - 当订单