Implementing Order Lifecycle Tracking and API Endpoints

Frontend Order Component Architecture

The order management interface consolidates all order states into a single view. A tabbed navigation system filters the display based on the order lifecycle: All, Unpaid, Shipped, Pending Review, and Refunds. A search input allows querying across the dataset.

<template>
  <div class="order-view">
    <nav class="status-tabs">
      <div :class="{ active: currentTab === 0 }" @click="switchTab(0)">All</div>
      <div :class="{ active: currentTab === 1 }" @click="switchTab(1)">Unpaid</div>
      <div :class="{ active: currentTab === 2 }" @click="switchTab(2)">Shipped</div>
      <div :class="{ active: currentTab === 3 }" @click="switchTab(3)">Pending Review</div>
      <div :class="{ active: currentTab === 4 }" @click="switchTab(4)">Refunds</div>
    </nav>
    <div class="search-panel">
      <input type="text" placeholder="Search orders..." v-model="searchQuery" />
      <button @click="executeSearch">Search</button>
    </div>
    <div v-if="isEmpty" class="empty-state">
      <img src="/assets/empty-cart.png" alt="No orders" />
      <p>No orders found</p>
      <router-link to="/catalog">Browse Products</router-link>
    </div>
    <div v-else class="order-list">
      <div class="order-card" v-for="order in visibleOrders" :key="order.id">
        <div class="card-header">
          <span class="store-name">Store: {{ order.storeName }}</span>
          <span class="order-status">{{ getStatusText(order.status) }}</span>
        </div>
        <div class="product-row" v-for="item in order.items" :key="item.id" @click="viewProduct(item.id)">
          <img :src="item.image" :alt="item.name" />
          <div class="product-info">
            <p>{{ item.name }}</p>
            <span>{{ item.specs }}</span>
          </div>
          <div class="product-pricing">
            <p>${{ item.price.toFixed(2) }}</p>
            <span>x{{ item.quantity }}</span>
          </div>
        </div>
        <div class="card-footer">
          <span>Total: ${{ order.totalAmount }}</span>
          <div class="actions">
            <button @click="removeOrder(order.id)">Delete</button>
            <button v-if="order.status === 1" @click="trackShipment(order.id)">Track</button>
            <button v-if="order.status === 0" @click="processPayment(order.id)">Pay</button>
            <button v-if="order.status === 1" @click="acknowledgeDelivery(order.id)">Received</button>
            <button v-if="order.status === 2" @click="submitReview(order.id)">Review</button>
            <button v-if="order.status === 3" @click="contactSupport(order.id)">Support</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import apiClient from '@/services/api';
import { showDialog, showToast } from 'vant';

export default {
  data() {
    return {
      currentTab: parseInt(this.$route.query.tab) || 0,
      searchQuery: '',
      isEmpty: true,
      allOrders: [],
      unpaidOrders: [],
      shippedOrders: [],
      reviewOrders: [],
      refundOrders: [],
      visibleOrders: []
    };
  },
  mounted() {
    this.fetchOrderData();
  },
  methods: {
    async fetchOrderData() {
      const userId = localStorage.getItem('userId');
      const fetchPromises = [
        apiClient.get('/orders', { params: { userId } }),
        apiClient.get('/orders', { params: { userId, status: 0 } }),
        apiClient.get('/orders', { params: { userId, status: 1 } }),
        apiClient.get('/orders', { params: { userId, status: 2 } }),
        apiClient.get('/orders', { params: { userId, status: 3 } })
      ];
      const [all, unpaid, shipped, review, refund] = await Promise.all(fetchPromises);
      this.allOrders = all.data;
      this.unpaidOrders = unpaid.data;
      this.shippedOrders = shipped.data;
      this.reviewOrders = review.data;
      this.refundOrders = refund.data;
      this.switchTab(this.currentTab);
    },
    switchTab(index) {
      this.currentTab = index;
      const mapping = {
        0: this.allOrders,
        1: this.unpaidOrders,
        2: this.shippedOrders,
        3: this.reviewOrders,
        4: this.refundOrders
      };
      const targetList = mapping[index] || [];
      this.isEmpty = targetList.length === 0;
      this.visibleOrders = targetList;
    },
    getStatusText(status) {
      const texts = ['Unpaid', 'Shipped', 'Pending Review', 'Completed'];
      return texts[status] || 'Unknown';
    },
    removeOrder(orderId) {
      showDialog({ title: 'Confirm Deletion', message: 'Are you sure?' }).then(() => {
        apiClient.delete(`/orders/${orderId}`).then(() => {
          showToast('Order deleted');
          this.fetchOrderData();
        });
      });
    },
    processPayment(orderId) {
      this.$router.push(`/checkout?orderId=${orderId}`);
    },
    acknowledgeDelivery(orderId) {
      showDialog({ title: 'Confirm Receipt', message: 'Have you received the items?' }).then(() => {
        apiClient.patch(`/orders/${orderId}/status`, { status: 2 }).then(() => {
          showToast('Receipt confirmed');
          this.fetchOrderData();
        });
      });
    },
    submitReview(orderId) {
      this.$router.push(`/reviews/new?orderId=${orderId}`);
    },
    contactSupport(orderId) {
      this.$router.push(`/support?orderId=${orderId}`);
    },
    viewProduct(productId) {
      this.$router.push(`/products/${productId}`);
    },
    trackShipment(orderId) {
      this.$router.push(`/orders/${orderId}/tracking`);
    },
    executeSearch() {
      // Search logic implementation
    }
  }
};
</script>

Backend Order Processing Endpoints

The server handles order operations through dedicated RESTful routes. State transitions (creation, payment confirmation, delivery acknowledgment) are managed by updating the order status field. Upon order generation, corresponding cart entries are automatically purged.

const express = require('express');
const router = express.Router();
const OrderModel = require('../models/Order');
const CartModel = require('../models/Cart');
const Database = require('../db/operations');
const { v4: uuidv4 } = require('uuid');

// Retrieve orders with optional status filter
router.get('/', async (req, res) => {
  const { userId, status, orderId } = req.query;
  const query = { userId };
  if (orderId) query.orderId = orderId;
  if (status !== undefined) query.status = parseInt(status);

  try {
    const orders = await Database.find(OrderModel, query, { projection: { _id: 0 } });
    res.({ code: 200, message: 'Orders retrieved', data: orders });
  } catch (error) {
    res.status(500).({ code: 500, message: 'Server error' });
  }
});

// Create new order and clear cart
router.post('/', async (req, res) => {
  const { userId, items, totalQuantity, totalAmount } = req.body;
  const orderId = `ORD-${uuidv4()}`;
  const creationDate = new Date().toISOString().split('T')[0];

  const orderItems = items.map(item => ({
    productId: item.productId,
    image: item.image,
    name: item.name,
    price: parseFloat(item.price),
    quantity: parseInt(item.quantity)
  }));

  try {
    await Database.insert(OrderModel, {
      orderId,
      userId,
      status: 0, // 0: Unpaid
      deliveryInfo: { name: '', phone: '', address: '', notes: '' },
      totalQuantity,
      totalAmount,
      creationDate,
      items: orderItems
    });

    // Remove purchased items from cart
    const cartDeletions = items.map(item =>
      Database.delete(CartModel, { cartId: item.cartId })
    );
    await Promise.all(cartDeletions);

    res.status(201).({ code: 201, message: 'Order created', data: { orderId } });
  } catch (error) {
    res.status(500).({ code: 500, message: 'Order creation failed' });
  }
});

// Update order status (e.g., payment, delivery)
router.patch('/:orderId/status', async (req, res) => {
  const { orderId } = req.params;
  const { status } = req.body;

  try {
    await Database.update(OrderModel, { orderId }, { $set: { status: parseInt(status) } });
    res.({ code: 200, message: 'Status updated' });
  } catch (error) {
    res.status(500).({ code: 500, message: 'Update failed' });
  }
});

// Attach delivery details to order
router.patch('/:orderId/delivery', async (req, res) => {
  const { orderId } = req.params;
  const { name, phone, address, notes } = req.body;

  try {
    await Database.update(OrderModel, { orderId }, { $set: { deliveryInfo: { name, phone, address, notes } } });
    res.({ code: 200, message: 'Delivery info updated' });
  } catch (error) {
    res.status(500).({ code: 500, message: 'Update failed' });
  }
});

// Delete an order
router.delete('/:orderId', async (req, res) => {
  const { orderId } = req.params;

  try {
    await Database.delete(OrderModel, { orderId });
    res.({ code: 200, message: 'Order deleted' });
  } catch (error) {
    res.status(500).({ code: 500, message: 'Deletion failed' });
  }
});

module.exports = router;

Tags: Vue.js Express.js Order Management REST API E-commerce

Posted on Wed, 21 Oct 2026 16:45:06 +0000 by djdon11