Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
5.9 KiB
5.9 KiB
Hình Ảnh - Thẻ Tham Khảo Nhanh
🎯 Tổng Quan
| Mục | Trạng Thái | Chi Tiết |
|---|---|---|
Thẻ HTML <img> |
✅ Tìm thấy 0 | Tất cả đã được thay thế bằng next/image |
| next/image Đã Dùng | ✅ 8 tệp | Triển khai đúng cách trên toàn ứng dụng |
| Thành Phần Hình Ảnh | ✅ 3 chuyên biệt | Gallery, Lightbox, Upload |
| Cấu Hình | ✅ Đã cấu hình | remotePatterns + tiêu đề CSP |
| Khả Năng Tiếp Cận | ✅ Hỗ trợ đầy đủ | Văn bản thay thế, điều hướng bàn phím, ARIA |
| Bảo Mật | ✅ Chỉ HTTPS | CSP đã cấu hình, blob URL cho xem trước |
📁 Vị Trí Sử Dụng Hình Ảnh
Thành Phần Hình Ảnh Cốt Lõi
components/listings/image-gallery.tsx ← Trình xem thư viện chính
components/listings/image-lightbox.tsx ← Xem toàn màn hình
components/listings/image-upload.tsx ← Tải lên với xem trước
Các Thành Phần Hiển Thị Hình Ảnh
components/search/property-card.tsx → Hình thu nhỏ trong kết quả tìm kiếm
components/agents/agent-profile-client.tsx → Ảnh đại diện + danh sách của môi giới
components/comparison/comparison-table.tsx → Hình ảnh so sánh
components/listings/listing-detail-client.tsx → Tích hợp ImageGallery
Thành Phần Trang
app/[locale]/(public)/listings/[id]/page.tsx → Chi tiết tin đăng (dùng ImageGallery)
app/[locale]/(public)/search/page.tsx → Kết quả tìm kiếm (dùng PropertyCard)
app/[locale]/(public)/agents/[id]/page.tsx → Hồ sơ môi giới
app/[locale]/(dashboard)/listings/page.tsx → Danh sách bảng điều khiển
app/[locale]/(dashboard)/listings/new/page.tsx → Tải lên tin đăng mới
🔧 Cấu Hình
next.config.js
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
],
}
Tiêu Đề CSP
img-src 'self' data: blob: https://*.mapbox.com https://*.tiles.mapbox.com https:
- ✅ Cho phép blob: (xem trước tệp)
- ✅ Cho phép data: (hình ảnh nội tuyến)
- ✅ Cho phép tất cả HTTPS
📊 Chi Tiết Thành Phần Hình Ảnh
ImageGallery
<ImageGallery
media={propertyMedia} // PropertyMedia[]
className="w-full"
/>
Tính năng: Ảnh chính + hình thu nhỏ, điều hướng, bộ đếm, tích hợp lightbox
ImageLightbox
<ImageLightbox
images={images}
initialIndex={0}
open={isOpen}
onClose={() => setIsOpen(false)}
/>
Tính năng: Điều hướng bàn phím, vuốt, tải trước, bẫy tiêu điểm
ImageUpload
<ImageUpload
images={uploadedImages}
onChange={setUploadedImages}
maxFiles={20}
/>
Tính năng: Kéo-thả, xác thực (JPEG/PNG/WebP), xem trước, dọn dẹp
🎨 Kiểu Dữ Liệu Hình Ảnh
interface PropertyMedia {
id: string;
url: string; // URL hình ảnh
type: 'image' | 'video'; // Loại phương tiện
order: number; // Thứ tự hiển thị
caption?: string; // Chú thích tùy chọn
}
interface ImageFile {
file: File; // Tệp trình duyệt
preview: string; // blob: URL
}
⚡ Tính Năng Hiệu Suất
| Tính Năng | Trạng Thái |
|---|---|
Kích thước thích ứng (thuộc tính sizes) |
✅ Đã triển khai |
| Tải ưu tiên cho nội dung phía trên nếp gấp | ✅ Đã triển khai |
| Tải trước hình ảnh trong lightbox | ✅ Đã triển khai |
| Dọn dẹp blob URL (bộ nhớ) | ✅ Đã triển khai |
| Placeholder xương | ⚠️ Chưa triển khai |
| Nén hình ảnh khi tải lên | ⚠️ Chưa triển khai |
♿ Tính Năng Khả Năng Tiếp Cận
| Tính Năng | Trạng Thái |
|---|---|
| Văn bản thay thế trên hình ảnh | ✅ Tiếng Việt |
| Nhãn ARIA | ✅ Đã triển khai |
| Điều hướng bàn phím | ✅ Phím mũi tên + Escape |
| Bẫy tiêu điểm trong hộp thoại | ✅ Đã triển khai |
| Bẫy tab | ✅ Đã triển khai |
🔒 Danh Sách Kiểm Tra Bảo Mật
- ✅ Mẫu remote chỉ dùng HTTPS
- ✅ Tiêu đề CSP đã cấu hình
- ✅ blob: URL chỉ dùng cho xem trước phía máy khách
- ⚠️ Xác thực URL hình ảnh tại API - CẦN LÀM
- ⚠️ Quét tệp tải lên của người dùng - CẦN LÀM
📝 Các Tác Vụ Thông Dụng
Thêm Hình Ảnh vào Thành Phần
import Image from 'next/image';
<Image
src={imageUrl}
alt="Văn bản mô tả bằng tiếng Việt"
fill
sizes="(max-width: 768px) 100vw, 50vw"
className="object-cover"
/>
Hiển Thị Thư Viện Hình Ảnh
import { ImageGallery } from '@/components/listings/image-gallery';
<ImageGallery
media={property.media}
/>
Tải Lên Tệp
import { ImageUpload } from '@/components/listings/image-upload';
const [images, setImages] = useState<ImageFile[]>([]);
<ImageUpload
images={images}
onChange={setImages}
maxFiles={20}
/>
🚨 Lưu Ý Quan Trọng
- Không bao giờ dùng thẻ HTML
<img>- Sử dụngnext/imagethay thế - Ngoại lệ: Xem trước blob URL trong image-upload là chấp nhận được
- Luôn cung cấp văn bản thay thế - Dùng văn bản tiếng Việt
- Dùng thuộc tính
sizes- Cho hình ảnh thích ứng - Đặt
priority- Cho hình ảnh phía trên nếp gấp - Thu hồi blob URL - Khi huỷ gắn kết để ngăn rò rỉ bộ nhớ
- Xác thực URL hình ảnh - Tại lớp API trước khi trả về
📞 Câu Hỏi?
Xem IMAGE_AUDIT_REPORT.md để biết chi tiết đầy đủ và các khuyến nghị.