align-content align-items flex-direction flex-wrap flex-flow 什么意思
时间: 2023-08-18 11:49:14 AIGC 浏览: 239
这些词是CSS中Flexbox布局的属性。
- align-content:定义了多根轴线(即多行)的对齐方式,只有在有多行时才会生效。常用的取值有:flex-start、flex-end、center、space-between、space-around、stretch等。
- align-items:定义了单根轴线(即一行)上的对齐方式。常用的取值有:flex-start、flex-end、center、baseline、stretch等。
- flex-direction:定义了主轴的方向。常用的取值有:row(默认值,主轴为水平方向)、row-reverse(主轴水平方向,但是起点在右边)、column(主轴为垂直方向)、column-reverse(主轴垂直方向,但是起点在下方)。
- flex-wrap:定义了是否换行。常用的取值有:nowrap(默认值,不换行)、wrap(换行,第一行在上方)、wrap-reverse(换行,第一行在下方)。
- flex-flow:是flex-direction和flex-wrap的缩写,用于同时定义主轴方向和是否换行。常用的取值有:row nowrap(默认值,主轴为水平方向,不换行)、column wrap(主轴为垂直方向,换行)等。
这些属性一起使用可以控制Flexbox布局的方向、对齐方式和是否换行等。
相关问题
align-items flex-direction flex-wrap flex-flow
这四个属性都是用于控制flex布局的。
- align-items用于设置flex容器内的所有项目在交叉轴上的对齐方式。
- flex-direction用于设置主轴的方向。
- flex-wrap用于设置是否换行。
- flex-flow是flex-direction和flex-wrap两个属性的缩写。
希望我的回答可以帮助到你!
display: flex; height: 80rpx; padding: 8rpx; justify-content: flex-start; align-items: center; flex-flow: row; background: #f8f8f8;
### 使用效果
以下是对这段 CSS 代码 `display: flex; height: 80rpx; padding: 8rpx; justify-content: flex-start; align-items: center; flex-flow: row; background: #f8f8f8;` 各属性的使用效果分析:
1. **`display: flex`**:将元素设置为 Flex 容器,其内部子元素可以使用 Flex 布局的特性进行排列。
2. **`height: 80rpx`**:设置容器的高度为 80rpx,`rpx` 是小程序中的响应式像素单位,可根据屏幕宽度进行自适应。
3. **`padding: 8rpx`**:为容器的四个方向都添加 8rpx 的内边距,使内容与容器边界之间有一定的间隔。
4. **`justify-content: flex-start`**:子元素在主轴(这里是水平方向,因为 `flex-flow: row`)上从起始位置开始排列,即左对齐。
5. **`align-items: center`**:子元素在交叉轴(垂直方向)上居中对齐。
6. **`flex-flow: row`**:其是 `flex-direction` 和 `flex-wrap` 的缩写属性,这里表示子元素在水平方向上排列,且不换行。
7. **`background: #f8f8f8`**:设置容器的背景颜色为浅灰色。
以下是一个示例代码,展示其实际效果:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.flex-container {
display: flex;
height: 80rpx;
padding: 8rpx;
justify-content: flex-start;
align-items: center;
flex-flow: row;
background: #f8f8f8;
}
.flex-item {
background-color: lightblue;
padding: 5px;
margin: 5px;
}
</style>
</head>
<body>
<div class="flex-container">
<div class="flex-item">Item 1</div>
<div class="flex-item">Item 2</div>
<div class="flex-item">Item 3</div>
</div>
</body>
</html>
```
### 优化建议
1. **兼容性考虑**:如果需要兼容不支持 `rpx` 单位的环境,可以考虑使用 `px` 或 `rem` 等单位替代。
2. **代码可读性**:可以将 `flex-flow` 属性拆分为 `flex-direction` 和 `flex-wrap`,这样更便于理解和维护。例如:
```css
.flex-container {
display: flex;
height: 80rpx;
padding: 8rpx;
justify-content: flex-start;
align-items: center;
flex-direction: row;
flex-wrap: nowrap;
background: #f8f8f8;
}
```
3. **响应式设计**:如果需要更好的响应式效果,可以结合媒体查询来调整容器的样式。例如:
```css
@media (max-width: 768px) {
.flex-container {
height: auto;
flex-direction: column;
}
}
```
阅读全文
相关推荐












【单选题 】(10分)下面那个属性可以设置盒子的内边距 A. white-space B. margin C. width D. padding 2.【单选题 】(10分)请说出外边距定义规则的含义 margin : 5px 6px 10px; A. 定义的是上、左右、下边距的值 B. 定义的是上下、左、右边距的值 C. 定义的是上、右、下边距的值 D. 定义的是上、下、左右边距的值 3.【单选题 】(10分)下面哪个属性用来设置flex布局容器内容在交叉轴上的对齐方式: A. align-content B. flex-direction C. justify-content D. align-items 4.【单选题 】(10分)关于css的ID规则和class样式规则的命名,下面说法错误的是 A. 在给选择器命名时不能使用- B. 不建议使用字母序列abc,姓名拼音,或文字拼音缩写这样的命名方式; C. 在保证区块位置的情况下,可以使用结构化命名方式; D. 建议使用语义化的命名方式; 5.【单选题 】(10分)在使用flex布局时,如果需要项目换行,需要设置哪个属性: A. flex-wrap B. flex-flow C. flex-direction D. display 6.【多选题 】 (10分)下面哪些技巧可以优化css的定义和使用 A. 使用继承。 B. 使用外部样式表文件; C. 使用分组选择器; D. 使用简化属性; 7.【多选题 】 (10分)下面哪些是组合器选择器? A. a#menu:visited B. li#first + li C. div#header > a.style:hover D. div#top li 8.【多选题 】 (10分) 首页 要闻 一点号 下面哪个样式定义方案可以把上面的无序列表变成横向导航: A. ul#menu li{float: left;};ul#menu a{display: inline-block;} B. ul#menu{position: relative;};ul#menu li{position: absolute;};ul#menu a{display: inline-block;} C. ul#menu li,ul#menu a{display: inline-block;} D. ul#menu{display:flex;};ul#menu a{display: inline-block;} 9.【多选题 】 (10分)关于使用ID选择器和Class选择器,下面说法正确的是 A. Class选择器适用于跨功能区域、跨标签或没有特定规律的多次调用的样式定义。 B. 在定义时,ID选择器使用#作为样式开始,而Class选择器以.作为样式开始; C. ID选择器用于页面中特定功能区域的id命名,但必须是唯一的 D. 一个ID选择器在一个页面中可以多次调用,但是一个Class选择器在一个页面中只能使用一次 10.【多选题 】 (10分)下面哪些是默认的块元素: A. div B. span C. a D. ul



Cart.php
<?php
/**
* Custom Cart Page for WooCommerce with Selective Checkout
* RECOMMENDED LOCATION (Woo override):
* /wp-content/themes/your-child-theme/woocommerce/cart/cart.php
* ALTERNATIVE (if your site is wired this way):
* /wp-content/themes/woodmart-child/cart.php
*/
if (!defined('ABSPATH')) {
exit;
}
do_action('woocommerce_before_cart');
// Provide tiny context for JS (invisible; no layout change)
$pc_cart_is_empty = WC()->cart->is_empty();
function pc_uid_for_view() {
if (is_user_logged_in()) return 'user_' . get_current_user_id();
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']));
}
$pc_uid = pc_uid_for_view();
?>
<form class="woocommerce-cart-form" action="<?php echo esc_url( wc_get_cart_url() ); ?>" method="post">
<?php do_action('woocommerce_before_cart_table'); ?>
<?php wp_nonce_field('woocommerce-cart', 'woocommerce-cart-nonce'); ?>
<input type="checkbox" id="select-all-items" />
<label for="select-all-items" style="display: inline-block; margin-left: 5px; cursor: pointer;">全选</label>
<?php esc_html_e('Product', 'woocommerce'); ?>
<?php esc_html_e('Price', 'woocommerce'); ?>
<?php esc_html_e('Quantity', 'woocommerce'); ?>
<?php esc_html_e('Subtotal', 'woocommerce'); ?>
<?php esc_html_e('操作', 'woocommerce'); ?>
<?php do_action('woocommerce_before_cart_contents'); ?>
<?php foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) : ?>
<?php
$_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);
?>
<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); ?>" />
<?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('%s', esc_url($product_permalink), $thumbnail); // PHPCS: XSS ok.
endif;
?>
<?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('%s', esc_url($product_permalink), $_product->get_name()), $cart_item, $cart_item_key) );
endif;
do_action('woocommerce_after_cart_item_name', $cart_item, $cart_item_key);
echo wc_get_formatted_cart_item_data($cart_item); // PHPCS: XSS ok.
?>
<?php
echo apply_filters('woocommerce_cart_item_price', WC()->cart->get_product_price($_product), $cart_item, $cart_item_key); // PHPCS: XSS ok.
?>
<?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
);
endif;
echo apply_filters('woocommerce_cart_item_quantity', $product_quantity, $cart_item_key, $cart_item); // PHPCS: XSS ok.
?>
保存中…
<?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.
?>
<?php
echo apply_filters('woocommerce_cart_item_remove_link', sprintf(
'×',
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);
?>
<?php endif; ?>
<?php endforeach; ?>
<?php do_action('woocommerce_after_cart_contents'); ?>
<?php do_action('woocommerce_after_cart_table'); ?>
</form>
<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>
<input type="text" name="coupon_code" class="input-text" id="coupon_code" value="" placeholder="输入优惠券代码" style="padding: 8px; width: 200px; border: 1px solid #ddd; border-radius: 4px; margin-right: 5px;" />
<button type="button" class="button" id="apply-coupon">应用优惠券</button>
已选商品: 0 件,共计: RM0.00
结算
<?php do_action('woocommerce_after_cart'); ?>
<style>
/* Your original styles exactly untouched */
<?php /* Keeping exactly your CSS content */ ?>
.cart-page-section {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.cart-page-section table.cart {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.cart-page-section table.cart th,
.cart-page-section table.cart td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #eee;
}
.cart-page-section table.cart th {
background-color: #f8f8f8;
font-weight: bold;
}
.cart-page-section table.cart th.product-select,
.cart-page-section table.cart td.product-select {
width: 8%;
text-align: center;
vertical-align: middle;
white-space: nowrap;
}
.cart-page-section table.cart td.product-info {
width: 37%;
vertical-align: top;
}
.cart-page-section table.cart .product-info .product-image {
float: left;
margin-right: 15px;
width: 100px;
height: 100px;
}
.cart-page-section table.cart .product-info .product-image img {
max-width: 100%;
height: auto;
display: block;
}
.cart-page-section table.cart .product-info .product-name {
overflow: hidden;
}
.cart-page-section table.cart td.product-price,
.cart-page-section table.cart td.product-quantity,
.cart-page-section table.cart td.product-subtotal {
width: 15%;
vertical-align: middle;
}
.cart-page-section table.cart td.product-remove {
width: 5%;
text-align: center;
vertical-align: middle;
}
.cart-footer-actions {
position: sticky;
bottom: 0;
background: #fff;
padding: 15px;
border-top: 1px solid #ddd;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
display: flex;
flex-direction: column;
gap: 15px;
}
.footer-left {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.coupon-section {
display: flex;
align-items: center;
gap: 5px;
}
.selected-summary {
font-size: 16px;
font-weight: bold;
flex: 1;
}
.cart-footer-actions .button {
padding: 10px 20px;
background-color: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
white-space: nowrap;
}
.cart-footer-actions .button:hover {
background-color: #d32f2f;
}
#partial-checkout {
padding: 12px 24px;
background-color: #2196F3;
color: white;
text-decoration: none;
border-radius: 4px;
display: inline-block;
transition: background-color 0.3s;
white-space: nowrap;
text-align: center;
font-weight: bold;
}
#partial-checkout:hover {
background-color: #0b7dda;
}
#coupon_code {
padding: 8px;
width: 200px;
border: 1px solid #ddd;
border-radius: 4px;
transition: border-color 0.3s;
}
#coupon_code:focus {
border-color: #2196F3;
outline: none;
}
@media (max-width: 768px) {
.cart-page-section table.cart thead { display: none; }
.cart-page-section table.cart td {
display: block;
width: 100% !important;
text-align: right;
padding: 10px;
position: relative;
padding-left: 50%;
}
.cart-page-section table.cart td::before {
content: attr(data-title);
position: absolute;
left: 15px;
font-weight: bold;
text-align: left;
}
.cart-page-section table.cart .product-info .product-image {
float: none;
margin: 0 auto 10px;
}
.cart-page-section table.cart td.product-select::before { content: "选择"; }
.cart-footer-actions { flex-direction: column; align-items: flex-start; }
.footer-left { width: 100%; justify-content: space-between; }
.coupon-section { width: 100%; margin-top: 10px; }
.coupon-section input { flex: 1; }
.cart-footer-actions .footer-bottom-row { flex-direction: column; align-items: stretch; }
.selected-summary { text-align: center; margin-bottom: 10px; }
#partial-checkout { width: 100%; padding: 15px; }
.cart-footer-actions .button { padding: 12px 15px; margin: 5px 0; width: 100%; text-align: center; }
}
@media (max-width: 480px) {
.cart-page-section { padding: 15px; }
.cart-page-section table.cart td { padding-left: 45%; }
.cart-page-section table.cart td::before { font-size: 14px; }
.cart-footer-actions { padding: 10px; }
#coupon_code { width: 100%; }
}
<style>
/* 添加加载动画样式 */
.quantity-saving-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
border-radius: 4px;
}
.quantity-saving-loader {
width: 24px;
height: 24px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 使数量输入框容器相对定位 */
.product-quantity .quantity {
position: relative;
}
</style>
<script>
jQuery(function($){
// Context
var PC = {
ajax_url: (window.wc_cart_params && window.wc_cart_params.ajax_url) || '<?php echo esc_url( admin_url("admin-ajax.php") ); ?>',
security: (function(){
var n = $('input[name="woocommerce-cart-nonce"]').val();
if (n) return n;
if (window.wc_cart_params && window.wc_cart_params.cart_nonce) return window.wc_cart_params.cart_nonce;
return '<?php echo wp_create_nonce("woocommerce-cart"); ?>';
})(),
cart_is_empty: <?php echo $pc_cart_is_empty ? 'true' : 'false'; ?>,
cart_uid: '<?php echo esc_js($pc_uid); ?>'
};
// Keys
function lsKey(name){ return 'pc_' + name + '_' + PC.cart_uid; }
var LS_SELECTION = lsKey('selected_items');
var LS_MASTER = lsKey('cart_master');
// Utilities
function getSelectedKeys(){
return $('.item-checkbox:checked').map(function(){ return this.value; }).get();
}
function fmtRM(n){ n = isNaN(n) ? 0 : n; return 'RM' + Number(n).toFixed(2); }
// Selection persistence
function readSelection(){ try { return JSON.parse(localStorage.getItem(LS_SELECTION) || '[]'); } catch(e){ return []; } }
function writeSelection(keys){ try { localStorage.setItem(LS_SELECTION, JSON.stringify(keys||[])); } catch(e){} }
function restoreSelectionFromLS(){
var saved = readSelection(); if (!Array.isArray(saved)) saved = [];
$('.item-checkbox').each(function(){ $(this).prop('checked', saved.indexOf(this.value) !== -1); });
}
window.addEventListener('storage', function(ev){
if (ev.key === LS_SELECTION) { restoreSelectionFromLS(); updateSelectedSummary(); }
});
// Selected summary
function updateSelectedSummary(){
var total = 0, count = 0;
$('.item-checkbox:checked').each(function(){
var price = parseFloat($(this).data('price'));
if (!isNaN(price)) { total += price; count++; }
});
$('#selected-count').text(count);
$('#selected-total').text(fmtRM(total));
var totalCbs = $('.item-checkbox').length;
var checkedCbs = $('.item-checkbox:checked').length;
var allChecked = totalCbs > 0 && checkedCbs === totalCbs;
$('#select-all-items, #footer-select-all').prop('checked', allChecked);
writeSelection(getSelectedKeys());
}
// Select all
$('#select-all-items, #footer-select-all').off('change.sc').on('change.sc', function(){
var checked = $(this).prop('checked');
$('.item-checkbox').prop('checked', checked);
updateSelectedSummary();
});
$(document).on('change', '.item-checkbox', updateSelectedSummary);
// Snapshot cart DOM -> localStorage (guest resilience)
function snapshotCartFromDOM() {
var items = [];
$('tr.cart_item').each(function(){
var $row = $(this);
var pid = parseInt($row.attr('data-product_id'),10)||0;
var vid = parseInt($row.attr('data-variation_id'),10)||0;
var qty = parseFloat($row.find('input.qty').val())||0;
var variation = {};
try { variation = JSON.parse($row.attr('data-variation')||'{}'); } catch(e){ variation = {}; }
if (pid && qty > 0) {
items.push({ product_id: pid, variation_id: vid, variation: variation, quantity: qty });
}
});
try { localStorage.setItem(LS_MASTER, JSON.stringify({ ts: Date.now(), items: items })); } catch(e){}
}
function maybeRehydrateFromLocal() {
if (!PC.cart_is_empty) return;
var raw = localStorage.getItem(LS_MASTER);
if (!raw) return;
var data; try { data = JSON.parse(raw); } catch(e){ return; }
var items = (data && data.items) ? data.items : [];
if (!items.length) return;
$.ajax({
url: PC.ajax_url, method: 'POST', dataType: 'json',
data: { action: 'pc_rehydrate_cart', security: PC.security, items: JSON.stringify(items) }
}).done(function(res){
if (res && res.success) {
// The current page is cart; reload to display rehydrated items
location.reload();
}
});
}
// Remove selected
$('#remove-selected-items').off('click.sc').on('click.sc', function(){
var keys = getSelectedKeys();
if (!keys.length) { alert('请选择要删除的商品'); return; }
if (!confirm('确定要删除选中的商品吗?')) return;
$.ajax({
url: PC.ajax_url, method: 'POST', dataType: 'json',
data: { action: 'remove_selected_cart_items', selected_items: keys, security: PC.security }
}).done(function(res){
if (res && res.success) {
var saved = readSelection().filter(function(k){ return keys.indexOf(k) === -1; });
writeSelection(saved);
location.reload();
} else {
alert((res && res.data && (res.data.message || res.data)) || '删除失败,请重试');
}
}).fail(function(){ alert('删除失败,请重试'); });
});
// Clear cart
$('#clear-cart').off('click.sc').on('click.sc', function(){
if (!confirm('确定要清空购物车吗?')) return;
$.ajax({
url: PC.ajax_url, method: 'POST', dataType: 'json',
data: { action: 'empty_cart', security: PC.security }
}).done(function(res){
if (res && res.success) {
writeSelection([]);
localStorage.removeItem(LS_MASTER);
location.reload();
} else {
alert((res && res.data && (res.data.message || res.data)) || '清空购物车失败,请重试');
}
}).fail(function(){ alert('清空购物车失败,请重试'); });
});
// Apply coupon
$('#apply-coupon').off('click.sc').on('click.sc', function(){
var code = ($.trim($('#coupon_code').val()) || '');
if (!code) { alert('请输入优惠券代码'); return; }
var $btn = $(this).prop('disabled', true).text('处理中...');
$.ajax({
url: PC.ajax_url, method: 'POST', dataType: 'json',
data: { action: 'apply_coupon', coupon_code: code, security: PC.security }
}).done(function(res){
if (res && res.success) {
location.reload();
} else {
alert((res && res.data && (res.data.message || res.data)) || '优惠券应用失败,请重试');
$btn.prop('disabled', false).text('应用优惠券');
}
}).fail(function(){
alert('发生错误,请重试');
$btn.prop('disabled', false).text('应用优惠券');
});
});
// Quantity auto-save (per-row only; no page hold)
// 修复后的数量自动保存功能
function parseCartKeyFromInputName(n){
// 修正的正则表达式 - 匹配: cart[<cart_item_key>][qty]
var m = (n||'').match(/^cart\[(.+?)\]\[qty\]$/);
return m ? m[1] : null;
}
// 创建加载动画元素
function createLoader() {
var $overlay = $('');
return $overlay;
}
// Quantity auto-save with loader effect
function parseCartKeyFromInputName(n){
// 修正正则表达式
var m = (n||'').match(/^cart\[(.+?)\]\[qty\]$/);
return m ? m[1] : null;
}
var qtyTimers = {};
$(document).on('input change', '.qty', function(){
var $input = $(this);
var key = parseCartKeyFromInputName($input.attr('name'));
if (!key) return;
var row = $input.closest('tr');
var $quantityContainer = $input.closest('.quantity');
var val = parseInt($input.val(), 10);
if (isNaN(val) || val < 0) val = 0;
// 清除现有定时器
if (qtyTimers[key]) clearTimeout(qtyTimers[key]);
// 创建加载动画容器(如果不存在)
if (!$quantityContainer.find('.quantity-saving-overlay').length) {
$quantityContainer.append(createLoader());
}
// 隐藏加载层
$quantityContainer.find('.quantity-saving-overlay').hide();
// 设置新的定时器
qtyTimers[key] = setTimeout(function(){
// 显示加载动画
$quantityContainer.find('.quantity-saving-overlay').show();
$input.prop('disabled', true);
$.ajax({
url: PC.ajax_url,
method: 'POST',
dataType: 'json',
data: {
action: 'update_cart_item_qty',
cart_item_key: key,
qty: val,
security: PC.security
}
}).done(function(res){
if (res && res.success && res.data){
if (res.data.subtotal_html) {
row.find('td.product-subtotal').html(res.data.subtotal_html);
}
// 更新复选框价格
var $cb = row.find('.item-checkbox');
if ($cb.length && typeof res.data.line_total_incl_tax === 'number') {
$cb.attr('data-price', res.data.line_total_incl_tax);
updateSelectedSummary();
}
// 数量为0时移除商品行
if (val === 0 || res.data.removed) {
row.fadeOut(300, function(){
$(this).remove();
snapshotCartFromDOM();
updateSelectedSummary();
});
} else {
snapshotCartFromDOM();
}
} else {
var msg = (res && res.data && (res.data.message || res.data)) || '数量更新失败';
alert(msg);
}
}).fail(function(){
alert('数量更新失败,请重试');
}).always(function(){
// 隐藏加载动画并启用输入框
$quantityContainer.find('.quantity-saving-overlay').hide();
$input.prop('disabled', false);
});
}, 500); // 0.5秒延迟
});
// 新增错误提示函数
function showError(msg) {
if (!$('#qty-error').length) {
$('body').append('');
}
$('#qty-error').text(msg).fadeIn(200).delay(3000).fadeOut(400);
}
// Partial checkout -> regular checkout page
$('#partial-checkout').off('click.sc').on('click.sc', function(e){
e.preventDefault();
var keys = getSelectedKeys();
if (!keys.length) { alert('请至少选择一件商品结算'); return; }
var $btn = $(this), t = $btn.text();
$btn.prop('disabled', true).text('创建订单中...');
// Snapshot first for safety
snapshotCartFromDOM();
$.ajax({
url: PC.ajax_url, method: 'POST', dataType: 'json',
data: { action: 'create_direct_order', selected_items: keys, security: PC.security }
}).done(function(res){
if (res && res.success && res.data && res.data.checkout_url) {
window.location.href = res.data.checkout_url; // This is /checkout/?pc_token=...
} else {
alert((res && res.data && (res.data.message || res.data)) || '创建订单失败,请重试');
$btn.prop('disabled', false).text(t);
}
}).fail(function(xhr){
var msg = '创建订单失败';
if (xhr && xhr.responseJSON && xhr.responseJSON.data) {
msg += ':' + (xhr.responseJSON.data.message || xhr.responseJSON.data);
}
alert(msg);
$btn.prop('disabled', false).text(t);
});
});
// Keep LS selection after removing via the "x" link and update cart snapshot
$(document).on('click', 'a.remove', function(){
var key = $(this).closest('tr.cart_item').attr('data-cart_item_key');
if (key) {
var saved = readSelection().filter(function(k){ return k !== key; });
writeSelection(saved);
snapshotCartFromDOM();
}
});
// Init
restoreSelectionFromLS();
updateSelectedSummary();
snapshotCartFromDOM();
maybeRehydrateFromLocal();
});
</script>
functions.php
<?php
defined('ABSPATH') || exit;
/**
* Robust partial checkout to regular /checkout/ with durable cart snapshot
* - Snapshot full cart before virtualizing selection for checkout
* - Do not remove anything until order is created
* - On success (thank-you), rebuild cart as (snapshot - purchased)
* - On cancel/back (visit cart), restore snapshot
* - Guest resilience: localStorage + rehydrate AJAX
*/
/* -------------------------------------------------
* Helpers
* ------------------------------------------------- */
function pc_get_cart_uid() {
if (is_user_logged_in()) {
return 'user_' . get_current_user_id();
}
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']));
}
function pc_build_item_key($product_id, $variation_id = 0) {
return (int)$product_id . '|' . (int)$variation_id;
}
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);
}
/* -------------------------------------------------
* AJAX: Local rehydrate when Woo cart is empty
* ------------------------------------------------- */
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' => 'Invalid items.'), 400);
}
if (!WC()->cart) wc_load_cart();
if (!WC()->cart->is_empty()) {
wp_send_json_success(array('message' => 'Cart not empty.'));
}
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: Update qty (per-row; no page reload)
* ------------------------------------------------- */
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' => 'Missing params.'), 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' => 'Cart item not found after update.'), 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);
// Use line_total + line_tax (after totals) for your checkbox data-price
$line_total_incl_tax = (float)($cart_item['line_total'] + $cart_item['line_tax']);
// functions.php 中的更新
wp_send_json_success(array(
'subtotal_html' => $subtotal_html,
'line_total_incl_tax' => $line_total_incl_tax,
'removed' => ($qty <= 0) // 明确返回是否移除
));
}
}
/* -------------------------------------------------
* AJAX: Remove selected
* ------------------------------------------------- */
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: Empty cart
* ------------------------------------------------- */
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: Apply coupon
* ------------------------------------------------- */
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) {
// Woo often pushes notices instead of bool; we return generic message
wp_send_json_error(array('message' => __('优惠券应用失败', 'woocommerce')), 400);
}
wp_send_json_success(true);
}
/* -------------------------------------------------
* AJAX: Start partial checkout to regular checkout page
* - Save snapshot + selected items in a transient keyed by token
* - Put token in session
* - Return /checkout/?pc_token=...
* ------------------------------------------------- */
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');
$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 full cart
$snapshot = pc_snapshot_current_cart();
// Build selected items from current cart based on cart_item_key list
$selected = array();
foreach (WC()->cart->get_cart() as $ci_key => $ci) {
if (!in_array($ci_key, $selected_keys, true)) {
continue;
}
$pid = (int)$ci['product_id'];
$vid = (int)$ci['variation_id'];
$qty = wc_stock_amount($ci['quantity']);
$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);
// Put token in session (used across checkout AJAX calls)
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));
}
/* -------------------------------------------------
* Virtualize cart on the regular checkout for token
* - On initial checkout load with ?pc_token=...
* - Ensure re-virtualization before checkout processing
* - Tag order with token
* - On thank-you, rebuild cart = snapshot - purchased
* - On returning to cart without completing: restore snapshot
* ------------------------------------------------- */
// Entering checkout with token: virtualize cart to selected items
add_action('woocommerce_before_checkout_form', function() {
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();
// Virtualize to selected items only
pc_restore_cart_from_items($payload['selected']);
// Persist token in session for all subsequent checkout AJAX calls
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', $token);
}
}, 1);
// Safety: just-in-time re-virtualization before order processing
add_action('woocommerce_before_checkout_process', function() {
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;
// Ensure cart still equals selected set (another tab might have restored)
pc_restore_cart_from_items($payload['selected']);
}, 1);
// Tag order with token so we can resolve on thank-you
add_action('woocommerce_checkout_create_order', function($order /* WC_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);
}
}, 10, 1);
// On thank-you: rebuild cart as (snapshot - purchased), then cleanup
add_action('woocommerce_thankyou', function($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'])) {
// Nothing to rebuild, just cleanup session token
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', null);
}
delete_transient(pc_transient_key($token));
return;
}
// Build purchased map pid|vid => qty
$purchased = array();
foreach ($order->get_items('line_item') 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 = snapshot - purchased
$remainder = array();
foreach ($payload['snapshot'] as $it) {
$pid = (int)$it['product_id'];
$vid = (int)$it['variation_id'];
$qty = wc_stock_amount($it['quantity']);
$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); // if over-purchased (unlikely), keep non-negative
}
}
// Rebuild cart to remainder
pc_restore_cart_from_items($remainder);
// Cleanup token
if (method_exists(WC()->session, 'set')) {
WC()->session->set('pc_partial_token', null);
}
delete_transient(pc_transient_key($token));
// Also update localStorage best-effort on the thank-you page (client side) is optional;
// your cart.php JS already re-snapshots on next visit.
}, 20);
// If user visits the cart page with an active partial token (abandoned/cancelled), restore full snapshot
add_action('woocommerce_before_cart', function() {
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['snapshot'])) return;
// Restore full snapshot so cart page always shows everything
pc_restore_cart_from_items($payload['snapshot']);
// Keep token so user can retry checkout; or clear it if you prefer ending the flow here:
// WC()->session->set('pc_partial_token', null);
}, 1);
Currently You are now a website builder. These are the code in my cart.php & functions.php. Currently, i am very satisfied with my layout. However, I want to achieve cart like Shopee / Taobao / Lazada, that allow my customer to
1) Do selective checkout, where only selected items are sent to checkout page, and after order created, only these items will be removed from cart. On the other side, non-selected items are retained inside cart, until customer remove them manually through 清除購物車 / 移除選中商品 or customer check out them. Only remove when order is created. If customer go back from checkout page or payment page, or anything, as long as order is not created, dont remove them. Currently im using restore cart way to achieve this, but everything from cart is removed currently after order created. theres some mistake that stop me from achieving this. I turned on feature where customer can see how many items are inside cart through the cart icon at header. At the same time, i dont want my customer to notice that their cart is cleared and recovered before (the amount of items in cart icon will show 0 when havent recover and the number of items after recover). I dont want them to notice the change, therefore, we have to recover it fast until customer did not notice them. Please be considerate regarding customer that use PC because they might access my website through another tab, at there, they can see the amount of items in cart icon. Therefore, i want it to always show the correct amount, only remove when the order is created and maintain if the order is not created. I dont want them to notice how it works (recovering)
2) All these
全选
刪除選中的商品
清空購物車
输入优惠券代码
应用优惠券
已选商品: 0 件,共计: RM0.00
结算
works perfectly fine, please remain them.
Maintaining all the layout and achieved functions. Please check which part stopped me from achieving the selective checkout (maintain non-selected items inside cart, only remove selected to checkout items when order created, and dont let customer nmotice the change in their cart. I want them to no matter access from where, either new tab, other device etc, they will see the correct items in their cart)
For now, If i cancel the payment halfway, i am logged out when back from payment page, and when i check from other tab, non-selected items are removed from cart, only selected items are still inside cart. Also, when i login back after payment page, it redirects me to pay-only page. Please refer how taobao cart works, and review which part is stopping me from achieve taobao cart, give me the part of code to be changed and the new code to replaced with the current one.



