在 WooCommerce 订单详情中添加历史订单时间线

在 WooCommerce 的订单详情页面中,只能查看当前订单的情况,而不能直接查看用户的所有订单情况,为了解决这个痛点,我写了一段代码, 可以实现显示当前客户的所有订单的时间线和订单汇总情况。

效果展示

  1. 顶部表格汇总了不同状态的订单数量和订单金额
  2. 按时间降序的方式展示了所有订单的总额与状态
  3. 点击订单号可以新窗口打开该订单的编辑页面
  4. 当前订单在时间线上显示为彩色阴影

代码展示

将以下代码添加到子主题的 functions.php 内即可

function order_timeline_display_meta_box($post)
{
    if (get_post_meta($post->ID, '_customer_user', true) == 0) {
        echo '<p>This is a guest order.</p>';
        return;
    }
    $customer_id = get_post_meta($post->ID, '_customer_user', true);
    $orders = wc_get_orders(array(
        'customer' => $customer_id,
        'limit' => -1,
    ));

    if (count($orders) > 1) {
        $order_status_count = array();
        $order_status_total = array();
        foreach ($orders as $order) {
            $order_status = $order->get_status();
            if (isset($order_status_count[$order_status])) {
                $order_status_count[$order_status] += 1;
                $order_status_total[$order_status] += $order->get_total();
            } else {
                $order_status_count[$order_status] = 1;
                $order_status_total[$order_status] = $order->get_total();
            }
        }
        echo '<table class="order-total-table">';
        echo '<tr><th>Status</th><th>Cnt</th><th>Total</th></tr>';
        foreach ($order_status_count as $status => $count) {
            $status_color = '';
            echo '<tr><td>' . ucfirst($status) . '</td><td>' . $count . '</td><td>' . wc_price($order_status_total[$status]) . '</td></tr>';
        }
        echo '</table>';
    }

    echo '<ul class="order-timeline-timeline">';
    foreach ($orders as $order) {
        $order_id = $order->get_id();
        $order_date = $order->get_date_created()->format('F j, Y');
        $order_total = $order->get_total();
        $order_status = $order->get_status();
        $currency_symbol = get_woocommerce_currency_symbol();
        $status_color = '';
        switch ($order_status) {
            case 'completed':
                $status_color = '#8bc34a';
                break;
            case 'processing':
                $status_color = '#ffc107';
                break;
            case 'pending':
                $status_color = '#03a9f4';
                break;
            case 'on-hold':
                $status_color = '#e91e63';
                break;
            case 'cancelled':
                $status_color = '#9e9e9e';
                break;
            case 'refunded':
                $status_color = '#ff5722';
                break;
            case 'failed':
                $status_color = '#f44336';
                break;
            default:
                $status_color = '#9e9e9e';
        }
        $class_current_order = '';
        if ($order_id == $post->ID) {
            $class_current_order = 'current-order';
        }

        echo '<li class="' . $class_current_order . '">';
        echo '<div class="order-timeline-timeline-card">';
        $order_edit_link = get_edit_post_link($order_id);

        echo '<div class="order-timeline-timeline-head"><div class="order-timeline-timeline-order-number">' . '<a href="' . $order_edit_link . '" target="_blank">#' . $order_id . '</a>' . '</div><div class="order-timeline-timeline-date">' . $order_date . '</div></div>';
        echo '<div class="order-timeline-timeline-bottom"><div class="order-timeline-timeline-status" style="background-color: ' . $status_color . ';">' . ucfirst($order_status) . '</div><div class="order-timeline-timeline-total">' . $currency_symbol . $order_total . '</div>';
        echo '</div>';
        echo '</li>';
    }
    echo '</ul>';

    ?>
    <style>
        .order-total-table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 10px;
            box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3);
            border-radius: 5px;
        }

        .order-total-table th {
            background-color: #f1f1f1;
        }

        .order-total-table tr:nth-child(odd) {
            background-color: #f1f1f1;
        }

        .order-total-table th,
        .order-total-table td {
            text-align: center;
            padding: 8px;
        }

        ul.order-timeline-timeline {
            list-style: none;
            margin: 0;
            padding: 0;
            position: relative;
        }

        ul.order-timeline-timeline li {
            position: relative;
            padding-left: 20px;
        }

        ul.order-timeline-timeline li::before {
            content: "";
            width: 12px;
            height: 12px;
            background-color: #ccc;
            border-radius: 50%;
            position: absolute;
            left: 1px;
            z-index: 1;
            transform: translateX(-50%);
        }

        ul.order-timeline-timeline li::after {
            content: "";
            position: absolute;
            width: 2px;
            height: calc(100% + 10px);
            background-color: #ccc;
            top: 8px;
            left: 0;
        }

        ul.order-timeline-timeline li .order-timeline-timeline-card {
            padding: 10px;
            box-sizing: border-box;
            box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3);
            border-radius: 5px;
        }

        ul.order-timeline-timeline li.current-order .order-timeline-timeline-card {
            box-shadow: 0 2px 2px 2px #2271b1a3;
        }

        ul.order-timeline-timeline li.current-order::before{
            background-color: #2271b1;
        }

        ul.order-timeline-timeline li .order-timeline-timeline-bottom,
        ul.order-timeline-timeline li .order-timeline-timeline-head {
            display: flex;
            justify-content: space-between;
            margin-bottom: 10px;
            align-items: flex-end;
        }

        ul.order-timeline-timeline li .order-timeline-timeline-order-number {
            font-weight: bold;
        }

        ul.order-timeline-timeline li .order-timeline-timeline-status {
            display: inline-block;
            color: #fff;
            font-weight: bold;
            padding: 4px 8px;
            border-radius: 5px;
        }

        ul.order-timeline-timeline li .order-timeline-timeline-total {
            font-weight: bold;
        }
    </style>
<?php
}

function order_timeline_plugin_add_meta_box()
{
    add_meta_box(
        'order-timeline-order-timeline',
        'Order Timeline',
        'order_timeline_display_meta_box',
        'shop_order',
        'side',
        'high'
    );
}
add_action('add_meta_boxes', 'order_timeline_plugin_add_meta_box');

概要

上述代码还可通过 Code Snippets 插件添加到网站后台中。
如果 WooCommerce 设置了 Guest 可直接下单,则可能出现订单未绑定客户的情况,不会展示客户的订单时间线。

第二版更新

更新内容

  1. 修复创建订单页面报错问题

完整代码

function order_timeline_display_meta_box($post)
{
    if ($post->post_status == 'auto-draft') {
        echo '<p>Timeline shows on the created orders.</p>';
        return;
    }
    
    if (get_post_meta($post->ID, '_customer_user', true) == 0) {
        echo '<p>This is a guest order.</p>';
        return;
    }
    $customer_id = get_post_meta($post->ID, '_customer_user', true);
    $orders = wc_get_orders(array(
        'customer' => $customer_id,
        'limit' => -1,
    ));

    if (count($orders) > 1) {
        $order_status_count = array();
        $order_status_total = array();
        foreach ($orders as $order) {
            $order_status = $order->get_status();
            if (isset($order_status_count[$order_status])) {
                $order_status_count[$order_status] += 1;
                $order_status_total[$order_status] += $order->get_total();
            } else {
                $order_status_count[$order_status] = 1;
                $order_status_total[$order_status] = $order->get_total();
            }
        }
        echo '<table class="order-total-table">';
        echo '<tr><th>Status</th><th>Cnt</th><th>Total</th></tr>';
        foreach ($order_status_count as $status => $count) {
            $status_color = '';
            echo '<tr><td>' . ucfirst($status) . '</td><td>' . $count . '</td><td>' . wc_price($order_status_total[$status]) . '</td></tr>';
        }
        echo '</table>';
    }

    echo '<ul class="order-timeline-timeline">';
    foreach ($orders as $order) {
        $order_id = $order->get_id();
        $order_date = $order->get_date_created()->format('F j, Y');
        $order_total = $order->get_total();
        $order_status = $order->get_status();
        $currency_symbol = get_woocommerce_currency_symbol();
        $status_color = '';
        switch ($order_status) {
            case 'completed':
                $status_color = '#8bc34a';
                break;
            case 'processing':
                $status_color = '#ffc107';
                break;
            case 'pending':
                $status_color = '#03a9f4';
                break;
            case 'on-hold':
                $status_color = '#e91e63';
                break;
            case 'cancelled':
                $status_color = '#9e9e9e';
                break;
            case 'refunded':
                $status_color = '#ff5722';
                break;
            case 'failed':
                $status_color = '#f44336';
                break;
            default:
                $status_color = '#9e9e9e';
        }
        $class_current_order = '';
        if ($order_id == $post->ID) {
            $class_current_order = 'current-order';
        }

        echo '<li class="' . $class_current_order . '">';
        echo '<div class="order-timeline-timeline-card">';
        $order_edit_link = get_edit_post_link($order_id);

        echo '<div class="order-timeline-timeline-head"><div class="order-timeline-timeline-order-number">' . '<a href="' . $order_edit_link . '" target="_blank">#' . $order_id . '</a>' . '</div><div class="order-timeline-timeline-date">' . $order_date . '</div></div>';
        echo '<div class="order-timeline-timeline-bottom"><div class="order-timeline-timeline-status" style="background-color: ' . $status_color . ';">' . ucfirst($order_status) . '</div><div class="order-timeline-timeline-total">' . $currency_symbol . $order_total . '</div>';
        echo '</div>';
        echo '</li>';
    }
    echo '</ul>';

    ?>
    <style>
        .order-total-table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 10px;
            box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3);
            border-radius: 5px;
        }

        .order-total-table th {
            background-color: #f1f1f1;
        }

        .order-total-table tr:nth-child(odd) {
            background-color: #f1f1f1;
        }

        .order-total-table th,
        .order-total-table td {
            text-align: center;
            padding: 8px;
        }

        ul.order-timeline-timeline {
            list-style: none;
            margin: 0;
            padding: 0;
            position: relative;
        }

        ul.order-timeline-timeline li {
            position: relative;
            padding-left: 20px;
        }

        ul.order-timeline-timeline li::before {
            content: "";
            width: 12px;
            height: 12px;
            background-color: #ccc;
            border-radius: 50%;
            position: absolute;
            left: 1px;
            z-index: 1;
            transform: translateX(-50%);
        }

        ul.order-timeline-timeline li::after {
            content: "";
            position: absolute;
            width: 2px;
            height: calc(100% + 10px);
            background-color: #ccc;
            top: 8px;
            left: 0;
        }

        ul.order-timeline-timeline li .order-timeline-timeline-card {
            padding: 10px;
            box-sizing: border-box;
            box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3);
            border-radius: 5px;
        }

        ul.order-timeline-timeline li.current-order .order-timeline-timeline-card {
            box-shadow: 0 2px 2px 2px #2271b1a3;
        }

        ul.order-timeline-timeline li.current-order::before{
            background-color: #2271b1;
        }

        ul.order-timeline-timeline li .order-timeline-timeline-bottom,
        ul.order-timeline-timeline li .order-timeline-timeline-head {
            display: flex;
            justify-content: space-between;
            margin-bottom: 10px;
            align-items: flex-end;
        }

        ul.order-timeline-timeline li .order-timeline-timeline-order-number {
            font-weight: bold;
        }

        ul.order-timeline-timeline li .order-timeline-timeline-status {
            display: inline-block;
            color: #fff;
            font-weight: bold;
            padding: 4px 8px;
            border-radius: 5px;
        }

        ul.order-timeline-timeline li .order-timeline-timeline-total {
            font-weight: bold;
        }
    </style>
<?php
}

function order_timeline_plugin_add_meta_box()
{
    add_meta_box(
        'order-timeline-order-timeline',
        'Order Timeline',
        'order_timeline_display_meta_box',
        'shop_order',
        'side',
        'high'
    );
}
add_action('add_meta_boxes', 'order_timeline_plugin_add_meta_box');