Now.js Framework Documentation

Now.js Framework Documentation

AuthLoadingManager - ระบบจัดการสถานะการโหลด

TH 31 Oct 2025 01:39

AuthLoadingManager - ระบบจัดการสถานะการโหลด

เอกสารสำหรับ AuthLoadingManager ซึ่งเป็นระบบจัดการสถานะการโหลด สำหรับการดำเนินการยืนยันตัวตน ของ Now.js Framework

📋 สารบัญ

  1. ภาพรวม
  2. การติดตั้งและนำเข้า (Installation & Import)
  3. การเริ่มต้นใช้งาน
  4. การดำเนินการโหลด
  5. ส่วนติดต่อผู้ใช้ระหว่างโหลด
  6. การติดตามความคืบหน้า
  7. เหตุการณ์การโหลด
  8. ตัวชี้วัดการโหลดกำหนดเอง
  9. การจัดการหมดเวลาโหลด
  10. ตัวอย่างการใช้งาน
  11. เอกสารอ้างอิง API
  12. แนวทางที่แนะนำ
  13. ข้อผิดพลาดที่พบบ่อย

ภาพรวม

AuthLoadingManager จัดการสถานะการโหลดสำหรับการดำเนินการยืนยันตัวตน เพื่อให้ feedback กับผู้ใช้ตลอดช่วงเวลาที่ระบบกำลังทำงาน

ฟีเจอร์หลัก

  • การติดตามการทำงาน: ติดตามสถานะการโหลดของแต่ละปฏิบัติการ
  • ส่วนติดต่อผู้ใช้ระหว่างโหลด: แสดงตัวบ่งชี้สถานะโหลดอัตโนมัติ
  • การติดตามความคืบหน้า: ติดตามเปอร์เซ็นต์ความคืบหน้าของงาน
  • ตัวชี้วัดหลายรูปแบบ: รองรับตัวบ่งชี้หลายประเภท เช่น สปินเนอร์ แถบความคืบหน้า หรือสเกเลตัน
  • ข้อความกำหนดเอง: กำหนดข้อความระหว่างโหลดได้เอง
  • การจัดการหมดเวลา: จัดการงานที่ใช้เวลานานเกินกำหนด
  • รองรับโอเวอร์เลย์: แสดงโอเวอร์เลย์เต็มหน้าจอระหว่างโหลด
  • รองรับการยกเลิก: ยกเลิกการทำงานที่กำลังโหลดอยู่ได้
  • ระบบเหตุการณ์: ส่งเหตุการณ์เมื่อสถานะการโหลดเปลี่ยนแปลง
  • ล้างสถานะอัตโนมัติ: เคลียร์สถานะการโหลดหลังจบงานอัตโนมัติ

เมื่อไหร่ควรใช้ AuthLoadingManager

ใช้ AuthLoadingManager เมื่อ:

  • ต้องการแสดง feedback ระหว่างขั้นตอนยืนยันตัวตน
  • ต้องการติดตามความคืบหน้าของแต่ละปฏิบัติการ
  • ต้องการประสบการณ์ UX ที่สม่ำเสมอระหว่างโหลด
  • ต้องการรองรับกรณีหมดเวลาระหว่างโหลด

ไม่ควรใช้เมื่อ:

  • ปฏิบัติการจบเร็วมาก (น้อยกว่า 100ms)
  • ไม่ต้องการแสดง UI ระหว่างโหลด

การติดตั้งและนำเข้า

AuthLoadingManager โหลดมาพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:

// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.AuthLoadingManager); // อ็อบเจ็กต์ AuthLoadingManager ที่ผูกไว้กับ window

การเริ่มต้นใช้งาน

การตั้งค่าพื้นฐาน

// AuthLoadingManager ทำงานร่วมกับ AuthManager อัตโนมัติ
// ไม่ต้องตั้งค่าแยกเพิ่มเติม

await AuthManager.init({
  enabled: true,

  // Loading configuration
  loading: {
    // แสดงตัวชี้วัดระหว่างโหลด
    enabled: true,

    // ระยะเวลาแสดงขั้นต่ำเพื่อป้องกันการกระพริบ
    minDuration: 300,

    // เวลาหมดในการโหลด (มิลลิวินาที)
    timeout: 30000,

    // ข้อความแสดงระหว่างโหลดค่าเริ่มต้น
    defaultMessage: 'Loading...',

    // ประเภทตัวชี้วัดการโหลด
  indicator: 'spinner',  // spinner, progress, skeleton, overlay

    // แสดงโอเวอร์เลย์
    overlay: true,

    // ความทึบของโอเวอร์เลย์
    overlayOpacity: 0.5
  }
});

console.log('AuthLoadingManager initialized!');

ลำดับการโหลด

เริ่มปฏิบัติการ
      ↓
┌─────────────────────┐
│  เริ่มแสดงสถานะโหลด   │
│  - แสดงตัวชี้วัด      │
│  - ส่งเหตุการณ์           │
└─────────┬───────────┘
          ↓
┌─────────────────────┐
│  ปฏิบัติการกำลังทำงาน │
│  - อัปเดตความคืบหน้า   │
│  - แสดงข้อความ             │
└─────────┬───────────┘
          ↓
┌─────────────────────┐
│  ตรวจสอบเวลาหมด        │
│  - ยกเลิกหากจำเป็น    │
│  - แสดงคำเตือน           │
└─────────┬───────────┘
          ↓
┌─────────────────────┐
│  ปฏิบัติการเสร็จสิ้น │
│  - ซ่อนตัวชี้วัด         │
│  - ส่งเหตุการณ์              │
│  - ทำความสะอาด                 │
└─────────────────────┘

การดำเนินการโหลด

AuthLoadingManager ติดตามสถานะการโหลดสำหรับปฏิบัติการเหล่านี้:

1. การเข้าสู่ระบบ

// แสดงสถานะโหลดระหว่างเข้าสู่ระบบ
await AuthManager.login({
  email: 'user@example.com',
  password: 'password'
});

// ระบบจะแสดงและซ่อนสถานะโหลดให้อัตโนมัติ

ข้อความระหว่างโหลด:

  • เริ่มต้น: "Logging in..."
  • สำเร็จ: "Login successful!"
  • ล้มเหลว: "Login failed"

2. การออกจากระบบ

// แสดงสถานะโหลดระหว่างออกจากระบบ
await AuthManager.logout();

ข้อความระหว่างโหลด:

  • เริ่มต้น: "Logging out..."
  • สำเร็จ: "Logged out"

3. การรีเฟรชโทเค็น

// แสดงสถานะโหลดระหว่างรีเฟรชโทเค็น
await AuthManager.refreshToken();

ข้อความระหว่างโหลด:

  • เริ่มต้น: "Refreshing session..."
  • สำเร็จ: "Session refreshed"
  • ล้มเหลว: "Session refresh failed"

4. การตรวจสอบเซสชัน

// แสดงสถานะโหลดระหว่างตรวจสอบเซสชัน
const isValid = await AuthManager.verifySession();

ข้อความระหว่างโหลด:

  • เริ่มต้น: "Verifying session..."
  • สำเร็จ: "Session verified"

5. การเข้าสู่ระบบผ่านโซเชียล

// แสดงสถานะโหลดระหว่างใช้ OAuth
await AuthManager.loginWithGoogle();

ข้อความระหว่างโหลด:

  • เริ่มต้น: "Connecting to Google..."
  • สำเร็จ: "Connected!"

6. การลงทะเบียน

// กำหนดปฏิบัติการเองพร้อมสถานะโหลด
await AuthLoadingManager.track('register', async () => {
  const response = await fetch('/api/auth/register', {
    method: 'POST',
    body: JSON.stringify(userData)
  });
  return response.json();
});

ข้อความระหว่างโหลด:

  • เริ่มต้น: "Creating account..."
  • สำเร็จ: "Account created!"

7. การรีเซ็ตรหัสผ่าน

await AuthLoadingManager.track('password_reset', async () => {
  return await resetPassword(email);
}, {
  message: 'Sending reset email...'
});

8. การอัปเดตโปรไฟล์

await AuthLoadingManager.track('profile_update', async () => {
  return await updateProfile(data);
}, {
  message: 'Updating profile...',
  showProgress: true
});

9. การยืนยันอีเมล

await AuthLoadingManager.track('email_verify', async () => {
  return await verifyEmail(code);
}, {
  message: 'Verifying email...',
  timeout: 10000
});

ส่วนติดต่อผู้ใช้ระหว่างโหลด

1. ตัวชี้วัดแบบสปินเนอร์

// สปินเนอร์ค่าเริ่มต้น
await AuthManager.init({
  loading: {
    indicator: 'spinner',
    message: 'Loading...'
  }
});

ตัวอย่าง HTML:

<div class="auth-loading">
  <div class="spinner"></div>
  <p>Loading...</p>
</div>

2. แถบความคืบหน้า

// แถบความคืบหน้าพร้อมเปอร์เซ็นต์
await AuthManager.init({
  loading: {
    indicator: 'progress',
    showPercentage: true
  }
});

// อัปเดตเปอร์เซ็นต์ความคืบหน้า
AuthLoadingManager.updateProgress('login', 50);  // 50%

ตัวอย่าง HTML:

<div class="auth-loading">
  <div class="progress-bar">
    <div class="progress-fill" style="width: 50%"></div>
  </div>
  <p>50% Complete</p>
</div>

3. โครง UI แบบสเกเลตัน

// โครง UI แบบสเกเลตันระหว่างโหลด
await AuthManager.init({
  loading: {
    indicator: 'skeleton',
  skeletonType: 'profile'  // profile, list, card
  }
});

ตัวอย่าง HTML:

<div class="auth-loading skeleton">
  <div class="skeleton-avatar"></div>
  <div class="skeleton-line"></div>
  <div class="skeleton-line short"></div>
</div>

4. โอเวอร์เลย์เต็มหน้าจอ

// โอเวอร์เลย์เต็มหน้าจอ
await AuthManager.init({
  loading: {
    indicator: 'overlay',
    overlay: true,
    overlayOpacity: 0.7,
    overlayColor: '#000000'
  }
});

ตัวอย่าง HTML:

<div class="auth-loading-overlay" style="opacity: 0.7">
  <div class="loading-content">
    <div class="spinner"></div>
    <p>Please wait...</p>
  </div>
</div>

5. ตัวชี้วัดกำหนดเอง

// ลงทะเบียนตัวชี้วัดกำหนดเอง
AuthLoadingManager.registerIndicator('custom', (options) => {
  const container = document.createElement('div');
  container.className = 'custom-loading';
  container.innerHTML = `
    <div class="custom-spinner"></div>
    <h3>${options.title || 'Loading'}</h3>
    <p>${options.message || 'Please wait...'}</p>
  `;
  return container;
});

// ใช้งานตัวชี้วัดกำหนดเอง
await AuthManager.init({
  loading: {
    indicator: 'custom',
    title: 'Authenticating',
    message: 'Verifying your credentials...'
  }
});

การติดตามความคืบหน้า

1. อัปเดตความคืบหน้าแบบกำหนดเอง

// ติดตามปฏิบัติการพร้อมความคืบหน้า
const operationId = AuthLoadingManager.start('upload', {
  message: 'Uploading file...',
  showProgress: true
});

// อัปเดตเปอร์เซ็นต์ความคืบหน้า
AuthLoadingManager.updateProgress(operationId, 25);   // 25%
AuthLoadingManager.updateProgress(operationId, 50);   // 50%
AuthLoadingManager.updateProgress(operationId, 75);   // 75%
AuthLoadingManager.updateProgress(operationId, 100);  // 100%

// จบสถานะโหลด
AuthLoadingManager.end(operationId);

2. ความคืบหน้าอัตโนมัติ

// ติดตามความคืบหน้าการอัปโหลดอัตโนมัติ
await AuthLoadingManager.trackWithProgress('upload', () => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        const percent = (e.loaded / e.total) * 100;
        AuthLoadingManager.updateProgress('upload', percent);
      }
    });

    xhr.addEventListener('load', () => resolve(xhr.response));
    xhr.addEventListener('error', reject);

    xhr.open('POST', '/api/upload');
    xhr.send(formData);
  });
}, {
  message: 'Uploading...',
  showProgress: true
});

3. ความคืบหน้าตามขั้นตอน

// ปฏิบัติการหลายขั้นตอน
const steps = [
  { name: 'validate', message: 'Validating data...' },
  { name: 'process', message: 'Processing...' },
  { name: 'save', message: 'Saving...' },
  { name: 'notify', message: 'Sending notifications...' }
];

const operationId = AuthLoadingManager.start('multi_step', {
  steps: steps,
  currentStep: 0
});

for (let i = 0; i < steps.length; i++) {
  const step = steps[i];

  // อัปเดตเป็นขั้นตอนปัจจุบัน
  AuthLoadingManager.updateStep(operationId, i, step.message);

  // ดำเนินงานในแต่ละขั้น
  await performStep(step.name);

  // อัปเดตเปอร์เซ็นต์ความคืบหน้า
  const progress = ((i + 1) / steps.length) * 100;
  AuthLoadingManager.updateProgress(operationId, progress);
}

AuthLoadingManager.end(operationId);

4. ความคืบหน้าแบบไม่ทราบระยะเวลา

// ปฏิบัติการที่ไม่ทราบระยะเวลา
const operationId = AuthLoadingManager.start('processing', {
  message: 'Processing...',
  indeterminate: true  // แสดงสปินเนอร์แทนแถบความคืบหน้า
});

await performLongOperation();

AuthLoadingManager.end(operationId);

เหตุการณ์การโหลด

เหตุการณ์ที่มีให้ใช้งาน

// ฟังเหตุการณ์เกี่ยวกับสถานะการโหลด

// 1. เริ่มโหลด
document.addEventListener('auth:loading:start', (e) => {
  const { operation, options } = e.detail;
  console.log(`Loading started: ${operation}`);
});

// 2. จบการโหลด
document.addEventListener('auth:loading:end', (e) => {
  const { operation, duration } = e.detail;
  console.log(`Loading ended: ${operation} (${duration}ms)`);
});

// 3. ความคืบหน้าเปลี่ยน
document.addEventListener('auth:loading:progress', (e) => {
  const { operation, progress } = e.detail;
  console.log(`Progress: ${progress}%`);
});

// 4. หมดเวลาระหว่างโหลด
document.addEventListener('auth:loading:timeout', (e) => {
  const { operation, timeout } = e.detail;
  console.log(`Operation timed out: ${operation}`);
});

// 5. ยกเลิกการโหลด
document.addEventListener('auth:loading:cancelled', (e) => {
  const { operation, reason } = e.detail;
  console.log(`Loading cancelled: ${operation}`);
});

// 6. เปลี่ยนขั้นตอน
document.addEventListener('auth:loading:step', (e) => {
  const { operation, step, total } = e.detail;
  console.log(`Step ${step}/${total}`);
});

ตัวชี้วัดการโหลดกำหนดเอง

1. สปินเนอร์กำหนดเอง

// ลงทะเบียนสปินเนอร์กำหนดเอง
AuthLoadingManager.registerIndicator('custom-spinner', (options) => {
  const div = document.createElement('div');
  div.className = 'custom-loading-spinner';
  div.innerHTML = `
    <style>
      .custom-loading-spinner {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 2rem;
      }

      .spinner-circle {
        width: 50px;
        height: 50px;
        border: 4px solid #f3f3f3;
        border-top: 4px solid #3498db;
        border-radius: 50%;
        animation: spin 1s linear infinite;
      }

      @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
      }

      .spinner-text {
        margin-top: 1rem;
        color: #666;
        font-size: 14px;
      }
    </style>

    <div class="spinner-circle"></div>
    <div class="spinner-text">${options.message || 'Loading...'}</div>
  `;

  return div;
});

// ใช้งานสปินเนอร์กำหนดเอง
await AuthManager.init({
  loading: {
    indicator: 'custom-spinner'
  }
});

2. แถบความคืบหน้าแบบแอนิเมชัน

AuthLoadingManager.registerIndicator('animated-progress', (options) => {
  const div = document.createElement('div');
  div.className = 'animated-progress-bar';
  div.innerHTML = `
    <style>
      .animated-progress-bar {
        width: 100%;
        max-width: 400px;
        margin: 2rem auto;
      }

      .progress-container {
        width: 100%;
        height: 8px;
        background: #e0e0e0;
        border-radius: 4px;
        overflow: hidden;
      }

      .progress-fill {
        height: 100%;
        background: linear-gradient(90deg, #4CAF50, #45a049);
        transition: width 0.3s ease;
        position: relative;
      }

      .progress-fill::after {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: linear-gradient(
          90deg,
          transparent,
          rgba(255,255,255,0.3),
          transparent
        );
        animation: shimmer 1.5s infinite;
      }

      @keyframes shimmer {
        0% { transform: translateX(-100%); }
        100% { transform: translateX(100%); }
      }

      .progress-text {
        text-align: center;
        margin-top: 0.5rem;
        font-size: 14px;
        color: #666;
      }
    </style>

    <div class="progress-container">
      <div class="progress-fill" style="width: 0%"></div>
    </div>
    <div class="progress-text">${options.message || 'Loading...'}</div>
  `;

  // เมธอดสำหรับอัปเดตความคืบหน้า
  div.updateProgress = (percent) => {
    const fill = div.querySelector('.progress-fill');
    const text = div.querySelector('.progress-text');
    fill.style.width = `${percent}%`;
    text.textContent = `${Math.round(percent)}% ${options.message || ''}`;
  };

  return div;
});

3. หน้าจอสเกเลตัน

AuthLoadingManager.registerIndicator('profile-skeleton', (options) => {
  const div = document.createElement('div');
  div.className = 'profile-skeleton';
  div.innerHTML = `
    <style>
      .profile-skeleton {
        padding: 2rem;
        max-width: 600px;
        margin: 0 auto;
      }

      .skeleton-item {
        background: linear-gradient(
          90deg,
          #f0f0f0 25%,
          #e0e0e0 50%,
          #f0f0f0 75%
        );
        background-size: 200% 100%;
        animation: loading 1.5s infinite;
        border-radius: 4px;
        margin-bottom: 1rem;
      }

      @keyframes loading {
        0% { background-position: 200% 0; }
        100% { background-position: -200% 0; }
      }

      .skeleton-avatar {
        width: 80px;
        height: 80px;
        border-radius: 50%;
        margin-bottom: 1rem;
      }

      .skeleton-line {
        height: 16px;
        margin-bottom: 0.5rem;
      }

      .skeleton-line.short {
        width: 60%;
      }
    </style>

    <div class="skeleton-avatar skeleton-item"></div>
    <div class="skeleton-line skeleton-item"></div>
    <div class="skeleton-line short skeleton-item"></div>
    <div class="skeleton-line skeleton-item"></div>
  `;

  return div;
});

การจัดการหมดเวลาโหลด

1. ตั้งค่าเวลาหมดสากล

// ตั้งค่าเวลาหมดระดับระบบ
await AuthManager.init({
  loading: {
    timeout: 30000,  // 30 seconds
    timeoutAction: 'cancel'  // cancel, notify, or ignore
  }
});

2. ตั้งค่าเวลาหมดรายปฏิบัติการ

// ตั้งเวลาหมดต่างกันในแต่ละปฏิบัติการ
await AuthLoadingManager.track('slow_operation', async () => {
  return await slowApiCall();
}, {
  timeout: 60000,  // 60 วินาทีสำหรับปฏิบัติการนี้
  timeoutAction: 'notify'
});

3. จัดการเมื่อหมดเวลา

// ฟังเหตุการณ์เวลาหมด
document.addEventListener('auth:loading:timeout', async (e) => {
  const { operation, timeout } = e.detail;

  console.log(`Operation ${operation} timed out after ${timeout}ms`);

  // ถามผู้ใช้ว่าจะรอต่อหรือยกเลิก
  const result = await showTimeoutDialog({
    message: 'Operation is taking longer than expected.',
    options: ['Continue Waiting', 'Cancel']
  });

  if (result === 'Cancel') {
    AuthLoadingManager.cancel(operation);
  } else {
    // ขยายเวลาให้ยาวขึ้น
    AuthLoadingManager.extendTimeout(operation, 30000);
  }
});

4. ยกเลิกปฏิบัติการ

// เริ่มปฏิบัติการที่ยกเลิกได้
const operationId = AuthLoadingManager.start('long_task', {
  message: 'Processing...',
  cancellable: true
});

// แสดงปุ่มยกเลิก
showCancelButton(() => {
  AuthLoadingManager.cancel(operationId);
});

// ดำเนินงานที่อาจใช้เวลานาน
try {
  await performLongTask();
  AuthLoadingManager.end(operationId);
} catch (error) {
  if (error.cancelled) {
    console.log('Operation cancelled by user');
  } else {
    throw error;
  }
}

ตัวอย่างการใช้งาน

1. การเข้าสู่ระบบพร้อม UI โหลดกำหนดเอง

// สร้าง UI ระหว่างโหลดสำหรับการเข้าสู่ระบบ
async function handleLogin(e) {
  e.preventDefault();

  const email = emailInput.value;
  const password = passwordInput.value;

  // เริ่มสถานะโหลดกำหนดเอง
  const loadingId = AuthLoadingManager.start('login', {
    message: 'Signing you in...',
    indicator: 'custom-spinner',
    overlay: true
  });

  try {
    // เรียกการเข้าสู่ระบบ
    await AuthManager.login({ email, password });

    // อัปเดตข้อความเมื่อสำเร็จ
    AuthLoadingManager.updateMessage(loadingId, 'Success! Redirecting...');

    // หน่วงเวลาเล็กน้อยเพื่อแสดงข้อความสำเร็จ
    await sleep(1000);

    // จบสถานะโหลด
    AuthLoadingManager.end(loadingId);

    // เปลี่ยนเส้นทางไปยังหน้าถัดไป
    await Router.navigate('/dashboard');
  } catch (error) {
    // จบสถานะโหลด
    AuthLoadingManager.end(loadingId);

    // แสดงข้อผิดพลาด
    showNotification(error.message, 'error');
  }
}

// ฟังก์ชันช่วยหน่วงเวลา
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

2. การลงทะเบียนหลายขั้นตอน

async function handleRegistration(userData) {
  const steps = [
    { name: 'validate', message: 'Validating information...' },
    { name: 'create', message: 'Creating account...' },
    { name: 'verify', message: 'Verifying email...' },
    { name: 'complete', message: 'Finalizing setup...' }
  ];

  const loadingId = AuthLoadingManager.start('register', {
    steps: steps,
    indicator: 'animated-progress',
    overlay: true
  });

  try {
    // ขั้นที่ 1: ตรวจสอบข้อมูล
    AuthLoadingManager.updateStep(loadingId, 0, steps[0].message);
    await validateUserData(userData);
    AuthLoadingManager.updateProgress(loadingId, 25);

    // ขั้นที่ 2: สร้างบัญชี
    AuthLoadingManager.updateStep(loadingId, 1, steps[1].message);
    const user = await createAccount(userData);
    AuthLoadingManager.updateProgress(loadingId, 50);

    // ขั้นที่ 3: ส่งอีเมลยืนยัน
    AuthLoadingManager.updateStep(loadingId, 2, steps[2].message);
    await sendVerificationEmail(user.email);
    AuthLoadingManager.updateProgress(loadingId, 75);

    // ขั้นที่ 4: ตั้งค่าขั้นสุดท้าย
    AuthLoadingManager.updateStep(loadingId, 3, steps[3].message);
    await completeUserSetup(user);
    AuthLoadingManager.updateProgress(loadingId, 100);

    // ข้อความเมื่อสำเร็จ
    AuthLoadingManager.updateMessage(loadingId, 'Registration complete!');
    await sleep(1500);

    AuthLoadingManager.end(loadingId);

    // ไปยังหน้าแดชบอร์ด
    await Router.navigate('/dashboard');
  } catch (error) {
    AuthLoadingManager.end(loadingId);
    showNotification(error.message, 'error');
  }
}

3. อัปโหลดไฟล์พร้อมความคืบหน้า

async function uploadProfilePicture(file) {
  const loadingId = AuthLoadingManager.start('upload', {
    message: 'Uploading profile picture...',
    showProgress: true,
    cancellable: true
  });

  // แสดงปุ่มยกเลิก
  const cancelBtn = showCancelButton();
  cancelBtn.onclick = () => {
    AuthLoadingManager.cancel(loadingId);
  };

  try {
    // สร้าง FormData สำหรับอัปโหลด
    const formData = new FormData();
    formData.append('file', file);

    // อัปโหลดพร้อมติดตามความคืบหน้า
    const response = await new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();

      // ติดตามเปอร์เซ็นต์การอัปโหลด
      xhr.upload.addEventListener('progress', (e) => {
        if (e.lengthComputable) {
          const percent = (e.loaded / e.total) * 100;
          AuthLoadingManager.updateProgress(loadingId, percent);
        }
      });

      // จัดการเมื่ออัปโหลดเสร็จ
      xhr.addEventListener('load', () => {
        if (xhr.status === 200) {
          resolve(JSON.parse(xhr.response));
        } else {
          reject(new Error('Upload failed'));
        }
      });

      xhr.addEventListener('error', () => reject(new Error('Network error')));
      xhr.addEventListener('abort', () => reject(new Error('Upload cancelled')));

      // ส่งคำขออัปโหลด
      xhr.open('POST', '/api/profile/picture');
      xhr.send(formData);

      // เก็บ xhr ไว้ใช้ยกเลิก
      AuthLoadingManager.setContext(loadingId, { xhr });
    });

    // เมื่อสำเร็จ
    AuthLoadingManager.updateMessage(loadingId, 'Upload complete!');
    await sleep(1000);
    AuthLoadingManager.end(loadingId);

    // ปรับปรุง UI ให้สะท้อนผลลัพธ์
    updateProfilePicture(response.url);
    showNotification('Profile picture updated!', 'success');
  } catch (error) {
    AuthLoadingManager.end(loadingId);

    if (error.message === 'Upload cancelled') {
      showNotification('Upload cancelled', 'info');
    } else {
      showNotification(error.message, 'error');
    }
  } finally {
    hideCancelButton();
  }
}

// จัดการเมื่อยกเลิกการอัปโหลด
document.addEventListener('auth:loading:cancel', (e) => {
  const { operation, context } = e.detail;

  if (operation === 'upload' && context.xhr) {
    context.xhr.abort();
  }
});

4. ตรวจสอบเซสชันพร้อมหน้าสเกเลตัน

async function initializeApp() {
  // แสดงสเกเลตันระหว่างตรวจสอบเซสชัน
  const loadingId = AuthLoadingManager.start('init', {
    indicator: 'profile-skeleton',
    message: 'Loading your profile...'
  });

  try {
    // ตรวจสอบสถานะเซสชัน
    const isAuthenticated = await AuthManager.verifySession();

    if (isAuthenticated) {
      // โหลดข้อมูลผู้ใช้
      const user = await loadUserData();

      // โหลดการตั้งค่าผู้ใช้
      const preferences = await loadUserPreferences();

      // เริ่มต้นแอปด้วยข้อมูลผู้ใช้
      await initializeAppWithUser(user, preferences);

      // จบสถานะโหลด
      AuthLoadingManager.end(loadingId);

      // แสดงส่วนหลักของแอป
      showMainApp();
    } else {
      // ยังไม่ได้ยืนยันตัวตน
      AuthLoadingManager.end(loadingId);

      // ไปยังหน้าล็อกอิน
      await Router.navigate('/login');
    }
  } catch (error) {
    AuthLoadingManager.end(loadingId);
    showErrorScreen(error);
  }
}

// เรียกฟังก์ชันเมื่อหน้าโหลดเสร็จ
initializeApp();

5. ปฏิบัติการแบบกลุ่มพร้อมความคืบหน้า

async function processUserData(users) {
  const total = users.length;

  const loadingId = AuthLoadingManager.start('batch_process', {
    message: `Processing ${total} users...`,
    showProgress: true,
    indeterminate: false
  });

  try {
    let processed = 0;
    let failed = 0;

    for (const user of users) {
      try {
        // ประมวลผลข้อมูลผู้ใช้แต่ละคน
        await processUser(user);
        processed++;
      } catch (error) {
        console.error(`Failed to process user ${user.id}:`, error);
        failed++;
      }

      // อัปเดตเปอร์เซ็นต์ความคืบหน้า
      const progress = (processed / total) * 100;
      AuthLoadingManager.updateProgress(loadingId, progress);

      // ปรับข้อความให้แสดงสถานะล่าสุด
      AuthLoadingManager.updateMessage(
        loadingId,
        `Processing: ${processed}/${total} (${failed} failed)`
      );
    }

    // เมื่อดำเนินการครบ
    AuthLoadingManager.updateProgress(loadingId, 100);
    AuthLoadingManager.updateMessage(
      loadingId,
      `Complete! Processed ${processed}, Failed ${failed}`
    );

    await sleep(2000);
    AuthLoadingManager.end(loadingId);

    // แสดงสรุปผล
    showBatchSummary({ processed, failed });
  } catch (error) {
    AuthLoadingManager.end(loadingId);
    showNotification('Batch processing failed', 'error');
  }
}

6. การหมดเวลาที่ให้ผู้ใช้ตัดสินใจ

async function performLongOperation() {
  const loadingId = AuthLoadingManager.start('long_op', {
    message: 'This may take a while...',
  timeout: 15000,  // 15 วินาที
    cancellable: true
  });

  try {
    // เริ่มปฏิบัติการ
    const operation = startLongOperation();

    // เก็บไว้สำหรับยกเลิก
    AuthLoadingManager.setContext(loadingId, { operation });

    // รอผลลัพธ์
    const result = await operation;

    AuthLoadingManager.end(loadingId);
    return result;
  } catch (error) {
    AuthLoadingManager.end(loadingId);
    throw error;
  }
}

// จัดการเมื่อหมดเวลา
document.addEventListener('auth:loading:timeout', async (e) => {
  const { operation } = e.detail;

  if (operation === 'long_op') {
    // แสดงไดอะล็อกให้เลือก
    const choice = await showDialog({
      title: 'Taking Longer Than Expected',
      message: 'The operation is taking longer than usual. What would you like to do?',
      buttons: [
        { label: 'Keep Waiting', value: 'wait' },
        { label: 'Cancel', value: 'cancel', variant: 'secondary' }
      ]
    });

    if (choice === 'cancel') {
      AuthLoadingManager.cancel(operation);
    } else {
      // ขยายเวลาต่ออีก 15 วินาที
      AuthLoadingManager.extendTimeout(operation, 15000);
    }
  }
});

// จัดการเมื่อถูกยกเลิก
document.addEventListener('auth:loading:cancelled', (e) => {
  const { operation, context } = e.detail;

  if (operation === 'long_op' && context.operation) {
    context.operation.cancel();
  }
});

เอกสารอ้างอิง API

เมธอด

start(operation, options)

เริ่มติดตามสถานะการโหลดสำหรับปฏิบัติการหนึ่ง

พารามิเตอร์:

  • operation (string) - รหัสระบุปฏิบัติการ
  • options (Object) - ตัวเลือกการตั้งค่าการโหลด

ค่าที่ส่งกลับ: string - ไอดีของปฏิบัติการ

ตัวอย่าง:

const id = AuthLoadingManager.start('login', {
  message: 'Logging in...',
  indicator: 'spinner',
  overlay: true
});

end(operationId)

ยุติสถานะการโหลดของปฏิบัติการ

พารามิเตอร์:

  • operationId (string) - ไอดีของปฏิบัติการ

ค่าที่ส่งกลับ: void

ตัวอย่าง:

AuthLoadingManager.end(operationId);

track(operation, fn, options)

ติดตามฟังก์ชันแบบอะซิงก์พร้อมสถานะการโหลด

พารามิเตอร์:

  • operation (string) - รหัสปฏิบัติการ
  • fn (Function) - ฟังก์ชันอะซิงก์ที่ต้องการติดตาม
  • options (Object) - ตัวเลือกการแสดงผล

ค่าที่ส่งกลับ: Promise<any> - ผลลัพธ์ของฟังก์ชัน

ตัวอย่าง:

const result = await AuthLoadingManager.track('api_call', async () => {
  return await fetch('/api/data').then(r => r.json());
}, {
  message: 'Loading data...'
});

updateProgress(operationId, percent)

อัปเดตเปอร์เซ็นต์ความคืบหน้า

พารามิเตอร์:

  • operationId (string) - ไอดีของปฏิบัติการ
  • percent (number) - ค่าเปอร์เซ็นต์ (0-100)

ค่าที่ส่งกลับ: void

ตัวอย่าง:

AuthLoadingManager.updateProgress(operationId, 50);

updateMessage(operationId, message)

ปรับข้อความระหว่างโหลด

พารามิเตอร์:

  • operationId (string) - ไอดีของปฏิบัติการ
  • message (string) - ข้อความใหม่

ค่าที่ส่งกลับ: void

ตัวอย่าง:

AuthLoadingManager.updateMessage(operationId, 'Almost done...');

updateStep(operationId, step, message)

อัปเดตขั้นตอนปัจจุบันของปฏิบัติการหลายขั้น

พารามิเตอร์:

  • operationId (string) - ไอดีของปฏิบัติการ
  • step (number) - ลำดับขั้นตอนปัจจุบัน
  • message (string) - ข้อความสำหรับขั้นตอนนั้น

ค่าที่ส่งกลับ: void

ตัวอย่าง:

AuthLoadingManager.updateStep(operationId, 2, 'Processing data...');

cancel(operationId)

ยกเลิกปฏิบัติการ

พารามิเตอร์:

  • operationId (string) - ไอดีของปฏิบัติการ

ค่าที่ส่งกลับ: void

ตัวอย่าง:

AuthLoadingManager.cancel(operationId);

extendTimeout(operationId, duration)

ขยายเวลาหมดสำหรับปฏิบัติการ

พารามิเตอร์:

  • operationId (string) - ไอดีของปฏิบัติการ
  • duration (number) - เวลาที่เพิ่มขึ้น (มิลลิวินาที)

ค่าที่ส่งกลับ: void

ตัวอย่าง:

AuthLoadingManager.extendTimeout(operationId, 30000);  // +30s

registerIndicator(name, factory)

ลงทะเบียนตัวชี้วัดการโหลดกำหนดเอง

พารามิเตอร์:

  • name (string) - ชื่อตัวชี้วัด
  • factory (Function) - ฟังก์ชันสร้าง DOM สำหรับตัวชี้วัด

ค่าที่ส่งกลับ: void

ตัวอย่าง:

AuthLoadingManager.registerIndicator('custom', (options) => {
  const div = document.createElement('div');
  div.innerHTML = '<div class="custom-loader"></div>';
  return div;
});

isLoading(operation)

ตรวจสอบว่าปฏิบัติการกำลังโหลดอยู่หรือไม่

พารามิเตอร์:

  • operation (string) - รหัสปฏิบัติการ

ค่าที่ส่งกลับ: boolean

ตัวอย่าง:

if (AuthLoadingManager.isLoading('login')) {
  console.log('Login in progress');
}

getLoadingState(operation)

ดึงสถานะการโหลดของปฏิบัติการ

พารามิเตอร์:

  • operation (string) - รหัสปฏิบัติการ

ค่าที่ส่งกลับ: Object|null - ข้อมูลสถานะการโหลดหรือ null

ตัวอย่าง:

const state = AuthLoadingManager.getLoadingState('login');
console.log(state.progress);  // Current progress

setContext(operationId, context)

จัดเก็บข้อมูลบริบทของปฏิบัติการ

พารามิเตอร์:

  • operationId (string) - ไอดีของปฏิบัติการ
  • context (Object) - ข้อมูลบริบท

ค่าที่ส่งกลับ: void

ตัวอย่าง:

AuthLoadingManager.setContext(operationId, { xhr, controller });

แนวทางที่แนะนำ

1. กำหนดเวลาขั้นต่ำเพื่อกันการกระพริบ

// ✅ ดี - ป้องกันการกระพริบของสถานะโหลด
await AuthManager.init({
  loading: {
    minDuration: 300  // แสดงอย่างน้อย 300 มิลลิวินาที
  }
});

// ❌ ไม่ดี - โหลดกระพริบเมื่อดำเนินการเร็วเกินไป
{
  minDuration: 0
}

2. เลือกตัวชี้วัดให้เหมาะกับงาน

// ✅ ดี - เลือกตัวชี้วัดเหมาะสม
const indicators = {
  'login': 'spinner',           // งานสั้น
  'upload': 'progress',         // ติดตามความคืบหน้าได้
  'init': 'skeleton',           // โหลดโครง UI
  'batch': 'progress'           // งานกลุ่มที่มีความคืบหน้า
};

// ❌ ไม่ดี - เลือกตัวชี้วัดไม่เหมาะ
{
  'upload': 'skeleton'  // ควรแสดงความคืบหน้า!
}

3. ใช้ข้อความที่ชัดเจน

// ✅ ดี - ข้อความชัดเจน
{
  message: 'Uploading profile picture...'
}

// ❌ ไม่ดี - ข้อความกว้างเกินไป
{
  message: 'Loading...'  // โหลดอะไรอยู่?
}

4. จัดการเวลาหมดอย่างนุ่มนวล

// ✅ ดี - จัดการเวลาหมด
document.addEventListener('auth:loading:timeout', async (e) => {
  const choice = await askUser('Continue or Cancel?');

  if (choice === 'continue') {
    AuthLoadingManager.extendTimeout(e.detail.operation, 30000);
  } else {
    AuthLoadingManager.cancel(e.detail.operation);
  }
});

// ❌ ไม่ดี - เพิกเฉยเวลาหมด
// ปฏิบัติการจะค้างอยู่

ข้อผิดพลาดที่พบบ่อย

1. ลืมจบสถานะการโหลด

// ❌ ไม่ดี - สถานะโหลดไม่ยอมจบ
const id = AuthLoadingManager.start('operation');
await doWork();
// ลืมเรียก AuthLoadingManager.end(id)!

// ✅ ดี - จบสถานะโหลดทุกครั้ง
const id = AuthLoadingManager.start('operation');
try {
  await doWork();
} finally {
  AuthLoadingManager.end(id);  // จบสถานะเสมอ
}

2. ลืมจัดการข้อผิดพลาด

// ❌ ไม่ดี - โหลดค้างเมื่อเกิดข้อผิดพลาด
const id = AuthLoadingManager.start('login');
await AuthManager.login(credentials);  // อาจโยนข้อผิดพลาด
AuthLoadingManager.end(id);

// ✅ ดี - จบสถานะโหลดแม้เกิดข้อผิดพลาด
const id = AuthLoadingManager.start('login');
try {
  await AuthManager.login(credentials);
} catch (error) {
  // จัดการข้อผิดพลาด
} finally {
  AuthLoadingManager.end(id);
}

3. มีสถานะโหลดซ้อนกันมากเกินไป

// ❌ ไม่ดี - มีโอเวอร์เลย์หลายชั้น
for (const item of items) {
  const id = AuthLoadingManager.start('process', { overlay: true });
  await process(item);
  AuthLoadingManager.end(id);
}
// โอเวอร์เลย์ซ้อนกันเต็มหน้าจอ!

// ✅ ดี - ใช้สถานะโหลดเดียวสำหรับงานชุด
const id = AuthLoadingManager.start('batch', { overlay: true });
for (let i = 0; i < items.length; i++) {
  await process(items[i]);
  AuthLoadingManager.updateProgress(id, (i / items.length) * 100);
}
AuthLoadingManager.end(id);

เอกสารเกี่ยวข้อง

  • Authentication.md - ภาพรวมระบบยืนยันตัวตน
  • AuthManager.md - ผู้จัดการการยืนยันตัวตนหลัก
  • AuthGuard.md - การป้องกันเส้นทาง
  • AuthErrorHandler.md - การจัดการข้อผิดพลาด
  • TokenService.md - การจัดการโทเค็น JWT