Заказ ожидает в Woocommerce как на маркетплейсе

woocommerce заказ на сборке
woocommerce заказ ожидает

Выведем шорткодом [ avsorder ] заказы в статусе «обработка», «доставка*».

*доставка это мой кастомный статус заказа, вы можете его удалить в сниппете ‘wc-delivery’

Не забудьте включить HPOS в Woocommerce! Если Вы не перешли на HPOS, то лучше перейти, так как почти все плагины уже работают с HPOS.


/**
 * Получить ID заказов пользователя по статусам (HPOS only) + кэш (12 часов)
 */
function avs_hpos_get_user_order_ids_by_statuses( $user_id, $statuses = array() ) {

    if ( ! $user_id || empty( $statuses ) || ! is_array( $statuses ) ) {
        return array();
    }

    // Проверяем HPOS
    if ( ! class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' )
      || ! \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) {
        return array();
    }

    $normalized_statuses = array_values(
        array_unique(
            array_filter(
                array_map(
                    static function( $status ) {
                        return strtolower( trim( (string) $status ) );
                    },
                    $statuses
                )
            )
        )
    );

    if ( empty( $normalized_statuses ) ) {
        return array();
    }

    sort( $normalized_statuses, SORT_STRING );

    $cache_key  = 'avs_hpos_order_ids_' . $user_id . '_' . md5( implode( '|', $normalized_statuses ) );
    $cache_ttl  = 12 * HOUR_IN_SECONDS;

    // Кэш
    $order_ids = get_transient( $cache_key );
    if ( false !== $order_ids ) {
        return $order_ids;
    }

    global $wpdb;
    $table_orders = $wpdb->prefix . 'wc_orders';

        $placeholders = implode( ', ', array_fill( 0, count( $normalized_statuses ), '%s' ) );
        $query        = "SELECT id
                                         FROM {$table_orders}
                                         WHERE type = 'shop_order'
                                             AND status IN ({$placeholders})
                                             AND customer_id = %d
                                         ORDER BY date_created_gmt DESC";
        $params       = array_merge( $normalized_statuses, array( (int) $user_id ) );

    $order_ids = $wpdb->get_col(
                $wpdb->prepare( $query, $params )
    );

    $order_ids = (array) $order_ids;
    set_transient( $cache_key, $order_ids, $cache_ttl );

    return $order_ids;
}

/**
 * Очистка кэша при изменении заказа
 */
function avs_hpos_clear_user_orders_cache( $order ) {

    if ( is_numeric( $order ) ) {
        $order = wc_get_order( $order );
    }

    if ( ! $order instanceof WC_Order ) return;
    if ( $order->get_type() !== 'shop_order' ) return;

    $user_id = (int) $order->get_customer_id();
    if ( $user_id <= 0 ) return;

    $statuses = array( 'wc-processing', 'wc-delivery' );
    sort( $statuses, SORT_STRING );

    $cache_key = 'avs_hpos_order_ids_' . $user_id . '_' . md5( implode( '|', $statuses ) );
    delete_transient( $cache_key );
}

add_action( 'woocommerce_order_status_changed', function( $oid, $old, $new, $order ) {
    avs_hpos_clear_user_orders_cache( $order ?: $oid );
}, 10, 4 );

/**
 * Получить данные по заказу
 */
function avs_get_order_preview_data( $order_id ) {

    $order = wc_get_order( $order_id );
    if ( ! $order ) return false;

    $items = $order->get_items( 'line_item' );
    if ( empty( $items ) ) return false;

    $items_arr = array_values( $items );
    $first     = $items_arr[0];
    $product   = wc_get_product( $first->get_product_id() );

    $thumb = $product ? wp_get_attachment_image_url( $product->get_image_id(), 'thumbnail' ) : '';
    if ( ! $thumb ) {
        $thumb = wc_placeholder_img_src( 'thumbnail' );
    }

    $status = $order->get_status(); // processing, delivery

    $status_text = '';
    if ( $status === 'processing' ) $status_text = 'Собираем и упаковываем';
    if ( $status === 'delivery' )   $status_text = 'Уже в пути';

    return array(
        'order_number' => $order->get_order_number(),
        'order_url'    => $order->get_view_order_url(),
        'thumb'        => $thumb,
        'items_count'  => count( $items ),
        'status_text'  => $status_text,
    );
}

/**
 * ШОРТКОД [avsorder]
 */
add_shortcode( 'avsorder', function() {

    $user_id = get_current_user_id();
    if ( ! $user_id ) return '';

    $order_ids = avs_hpos_get_user_order_ids_by_statuses( $user_id, array( 'wc-processing', 'wc-delivery' ) );
    if ( empty( $order_ids ) ) return '';

    $hide_nav = count( $order_ids ) <= 1;

    $html = '<div class="avs-slider-wrapper">
                <div class="avs-slider">
                    <div class="avs-slider-track">';

    foreach ( $order_ids as $oid ) {

        $d = avs_get_order_preview_data( $oid );
        if ( ! $d ) continue;

        $badge = ($d['items_count'] > 1)
            ? '<span class="avs-order-badge">'.intval($d['items_count']).'</span>' 
            : '';

        $html .= '
            <div class="avs-slide">
                <a class="avs-order-card" href="'.esc_url( $d['order_url'] ).'">
                    <div class="avs-order-img-wrap">
                        <img src="'.esc_url( $d['thumb'] ).'" class="avs-order-img" loading="lazy" decoding="async"/>
                        '.$badge.'
                    </div>
                    <div class="avs-order-info">
                        <div class="avs-order-status">'.esc_html( $d['status_text'] ).'</div>
                        <div class="avs-order-title">Заказ №'.esc_html( $d['order_number'] ).'</div>
                    </div>
                </a>
            </div>';
    }

    $html .= '
                    </div>
                </div>';

    if ( ! $hide_nav ) {
        $html .= '
            <div class="avs-slider-arrows">
                <button class="avs-prev">‹</button>
                <button class="avs-next">›</button>
            </div>

            <div class="avs-slider-dots"></div>';
    }

    $html .= '</div>';

    return $html;
});

На страницу где выводите шорткод добавьте виджет HTML вниз страницы и вставьте:

<script>
document.addEventListener('DOMContentLoaded', function () {
    const slider = document.querySelector('.avs-slider');
    if (!slider) return;

    const track  = slider.querySelector('.avs-slider-track');
    const slides = slider.querySelectorAll('.avs-slide');
    if (!track || !slides.length) return;

    const prev = slider.parentElement.querySelector('.avs-prev');
    const next = slider.parentElement.querySelector('.avs-next');
    const dots = slider.parentElement.querySelector('.avs-slider-dots');

    const hasNav = slides.length > 1;
    let index = 0;

    if (hasNav && dots) {
        dots.innerHTML = '';
        slides.forEach((_, i) => {
            const dot = document.createElement('span');
            dot.className = 'avs-dot' + (i === 0 ? ' active' : '');
            dot.dataset.index = i;
            dots.appendChild(dot);
        });
    }

    const update = () => {
        track.style.transform = 'translateX(' + (-index * 100) + '%)';
        if (hasNav && dots) {
            dots.querySelectorAll('.avs-dot').forEach(d => d.classList.remove('active'));
            const activeDot = dots.querySelector('[data-index="' + index + '"]');
            if (activeDot) activeDot.classList.add('active');
        }
    };

    if (hasNav && prev) {
        prev.addEventListener('click', () => {
            index = (index === 0) ? slides.length - 1 : index - 1;
            update();
        });
    }

    if (hasNav && next) {
        next.addEventListener('click', () => {
            index = (index === slides.length - 1) ? 0 : index + 1;
            update();
        });
    }

    if (hasNav && dots) {
        dots.addEventListener('click', (e) => {
            if (e.target.classList.contains('avs-dot')) {
                index = parseInt(e.target.dataset.index, 10);
                update();
            }
        });
    }

    update();
});
</script>

Ну и css:

/* ОСНОВНОЙ СЛАЙДЕР */
.avs-slider-wrapper {
    position: relative;
    width: 100%;
    padding: 0 24px;
    box-sizing: border-box;
    border:1px solid#D1D1D1;border-radius:10px;
    margin:8px 0px;}

.avs-slider {position: relative;
    overflow: hidden;
    width: 100%;}

.avs-slider-track {display: flex;
    transition: transform 0.35s ease;}

.avs-slide {min-width: 100%;}

/* КАРТОЧКИ */
.avs-order-card {display: flex;
    gap: 10px;
    padding:6px;
    align-items: center;}

.avs-order-img-wrap{width:60px;height: 60px;position: relative;}
.avs-order-img-wrap img {width: 60px;
    height: 60px;
    object-fit: cover;
    border-radius: 10px;
    border: 1px solid #B3B3B3;}

.avs-order-info {flex: 1;}
.avs-order-title {font-size: 13px;color: #939393;font-weight: 500;line-height: 1.1em;}
.avs-order-status{font-size: 13px;color: #434343;}

.avs-order-badge {position: absolute;
    bottom: -3px;
    right: -3px;
    width: 18px;
    height: 18px;
    font-size: 11px;
    border-radius: 50%;
    background: #fff;
    border: 1px solid #7F7F7F;
    display: flex;
    align-items: center;
    justify-content: center;}

/* СТРЕЛКИ */
.avs-slider-arrows {position: absolute;
    top: 50%;
    left: 0;
    right: 0;}

.avs-prev,
.avs-next {
    pointer-events: all;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width:30px;
    background:transparent!important;
    padding: 0!important;
    cursor: pointer;
    font-size:34px!important;
    color:#B0B0B0!important;
    display: flex;
    align-items: center;
    justify-content: center;}

.avs-prev { left: 0; }
.avs-next { right: 0; }

/* ТОЧКИ */
.avs-slider-dots {display: flex;
    justify-content: center;
    gap: 6px;
    margin-top:1px;margin-bottom:4px;}

.avs-dot {width: 5px;
    height: 5px;
    background: #D2D2D2;
    cursor: pointer;
    border-radius: 50%;}

.avs-dot.active {background: #A2A2A2;}

Уточнение, кол-во в круглишке считает кол-во позиций, а не кол-во товаров в штуках, так как товары могут быть 2,5 штуки/кг.

Зачем стоит кеш 12ч в php коде?

Если у пользователя десятки заказов — запрос каждый раз будет нагружать базу. Чтобы уменьшить нагрузку, результат запроса сохраняется во временный кэш WordPress (transient) на 12 часов.

Если шорткод выводите в попапе, то js будет:

<script>
document.addEventListener("DOMContentLoaded", function () {

    jQuery(document).on('elementor/popup/show', function (event, popupId) {

        if (Number(popupId) !== 62858) return; // только этот попап

        const popup = document.querySelector('#elementor-popup-modal-62858');
        if (!popup) return;

        const slider = popup.querySelector('.avs-slider');
        if (!slider) return;

        if (slider._avsInstance && typeof slider._avsInstance.reset === 'function') {
            slider._avsInstance.reset();
            return;
        }

        const track  = slider.querySelector('.avs-slider-track');
        const slides = slider.querySelectorAll('.avs-slide');
        if (!track || !slides.length) return;

        const prev   = popup.querySelector('.avs-prev');
        const next   = popup.querySelector('.avs-next');
        const dots   = popup.querySelector('.avs-slider-dots');

        const hasNav = slides.length > 1;
        let index = 0;

        /* создаём точки */
        if (hasNav && dots && !dots.dataset.ready) {
            dots.innerHTML = ""; // защита от повторного заполнения
            slides.forEach((s, i) => {
                const dot = document.createElement('span');
                dot.className = 'avs-dot' + (i === 0 ? ' active' : '');
                dot.dataset.index = i;
                dots.appendChild(dot);
            });
            dots.dataset.ready = "1";
        }

        const update = () => {
            track.style.transform = 'translateX(' + (-index * 100) + '%)';
            if (hasNav && dots) {
                dots.querySelectorAll('.avs-dot').forEach(d => d.classList.remove('active'));
                const activeDot = dots.querySelector('[data-index="' + index + '"]');
                if (activeDot) {
                    activeDot.classList.add('active');
                }
            }
        };

        /* стрелки */
        if (hasNav && prev) {
            prev.addEventListener('click', () => {
                index = (index === 0) ? slides.length - 1 : index - 1;
                update();
            });
        }

        if (hasNav && next) {
            next.addEventListener('click', () => {
                index = (index === slides.length - 1) ? 0 : index + 1;
                update();
            });
        }

        /* точки */
        if (hasNav && dots) {
            dots.addEventListener('click', (e) => {
                if (e.target.classList.contains('avs-dot')) {
                    index = parseInt(e.target.dataset.index, 10);
                    update();
                }
            });
        }

        slider._avsInstance = {
            reset() {
                index = 0;
                update();
            }
        };

        update();
    });
});
</script>
Picture of Автор: Александра

Автор: Александра

@avsalexandra
Занимаюсь натуральным питанием собак и кошек BARF. Wordpress для души ☺️

Crocoblock
Elementor
Gutenberg
Jetengine
Jetformbuilder
profile builder
Woocommerce
Wordpress
WYSIWYG
Лейка
#автосохранение
#доменная почта
#рассылка
#бейдж
#благотворительность
#заказ ожидает
#подарок
#подчёркивание
#публикация постов
#видео
#пожертвования
#мультивыбор
#роли
#drag and drop
#изображения товаров
#подписки
#распродажа
#личный кабинет
#пагинация
#alt text
#галерея товара
#аватар
#возврат
#видео товара
#купон
#отменить заказ
Комментарии:

Добавить комментарий