diff --git a/app/download/ApkTable.tsx b/app/download/ApkTable.tsx
new file mode 100644
index 0000000..4e3ca6d
--- /dev/null
+++ b/app/download/ApkTable.tsx
@@ -0,0 +1,75 @@
+'use client';
+
+import { formatFileSize, formatDateTime } from '@/lib/utils';
+
+interface Apk {
+ name: string;
+ commit: string | null;
+ size: number;
+ modifiedAt: string;
+ downloadUrl: string;
+}
+
+interface ApkTableProps {
+ apks: Apk[];
+}
+
+export default function ApkTable({ apks }: ApkTableProps) {
+ if (!apks || apks.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+ |
+ Commit ID
+ |
+
+ 文件大小
+ |
+
+ 创建时间
+ |
+
+ 操作
+ |
+
+
+
+ {apks.map((apk) => (
+
+ |
+
+ {apk.commit || '-'}
+
+ |
+
+ {formatFileSize(apk.size)}
+ |
+
+ {formatDateTime(new Date(apk.modifiedAt))}
+ |
+
+
+ 下载
+
+ |
+
+ ))}
+
+
+
+ );
+}
diff --git a/app/download/__tests__/ApkTable.test.tsx b/app/download/__tests__/ApkTable.test.tsx
new file mode 100644
index 0000000..677e129
--- /dev/null
+++ b/app/download/__tests__/ApkTable.test.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import ApkTable from '../ApkTable';
+
+describe('ApkTable', () => {
+ const mockApks = [
+ {
+ name: 'ft_timeshift_0_42_57ef3a60d.apk',
+ commit: '57ef3a60d',
+ size: 15728640,
+ modifiedAt: '2026-03-04T14:30:00.000Z',
+ downloadUrl: '/api/download?project=FT&version=0_42&filename=ft_timeshift_0_42_57ef3a60d.apk',
+ },
+ ];
+
+ it('should render table with correct columns', () => {
+ render();
+
+ expect(screen.getByText('Commit ID')).toBeInTheDocument();
+ expect(screen.getByText('文件大小')).toBeInTheDocument();
+ expect(screen.getByText('创建时间')).toBeInTheDocument();
+ });
+
+ it('should render APK data correctly', () => {
+ render();
+
+ expect(screen.getByText('57ef3a60d')).toBeInTheDocument();
+ expect(screen.getByText('15.00 MB')).toBeInTheDocument();
+ expect(screen.getByText('2026-03-04 22:30')).toBeInTheDocument();
+ expect(screen.getByText('下载')).toBeInTheDocument();
+ });
+
+ it('should show empty state when no APKs', () => {
+ render();
+
+ expect(screen.getByText('暂无 APK 文件')).toBeInTheDocument();
+ });
+});