feat: update page list show

This commit is contained in:
yanghao05
2025-04-08 15:35:58 +08:00
parent 7d28dff65b
commit cf8bb5f192
13 changed files with 280 additions and 102 deletions

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>趣云管理后台</title>
<title>Hello</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<meta name="description" content="趣云内容管理系统后台">
</head>

View File

@@ -37,7 +37,7 @@ const navItems = ref([
<Menubar :model="navItems" class="!rounded-none">
<template #start>
<div class="flex items-center pr-4">
<h2 class="m-0 text-2xl text-primary font-semibold">Admin Panel</h2>
<h2 class="m-0 text-2xl text-primary font-semibold">Panel</h2>
</div>
</template>
<template #end>

View File

@@ -0,0 +1,52 @@
{
"page": 0,
"limit": 0,
"total": 5,
"items": [
{
"id": 5,
"name": "test 05",
"upload_time": "2025-04-07 16:44:26",
"file_size": 100,
"media_type": "application/zip",
"file_type": "archive",
"thumbnail_url": ""
},
{
"id": 4,
"name": "test 04",
"upload_time": "2025-04-07 16:44:26",
"file_size": 100,
"media_type": "image/jpeg",
"file_type": "image",
"thumbnail_url": ""
},
{
"id": 3,
"name": "test 03",
"upload_time": "2025-04-07 16:44:26",
"file_size": 100,
"media_type": "application/pdf",
"file_type": "pdf",
"thumbnail_url": ""
},
{
"id": 2,
"name": "test 02",
"upload_time": "2025-04-07 16:44:26",
"file_size": 100,
"media_type": "audio/mp3",
"file_type": "unknown",
"thumbnail_url": ""
},
{
"id": 1,
"name": "test 01",
"upload_time": "2025-04-07 16:44:26",
"file_size": 100,
"media_type": "video/mp4",
"file_type": "video",
"thumbnail_url": ""
}
]
}

View File

@@ -2,7 +2,7 @@ import axios from 'axios';
// Create axios instance with default config
const httpClient = axios.create({
baseURL: '/api',
baseURL: '/v1',
timeout: 10000,
headers: {
'Content-Type': 'application/json',

View File

@@ -0,0 +1,9 @@
import httpClient from './httpClient';
export const mediaService = {
getMedias({ page = 1, limit = 10 } = {}) {
return httpClient.get('/admin/medias', {
params: { page, limit }
});
},
};

View File

@@ -1,4 +1,5 @@
<script setup>
import { mediaService } from '@/api/mediaService';
import { InputText } from 'primevue';
import Badge from 'primevue/badge';
import Button from 'primevue/button';
@@ -12,7 +13,7 @@ import ProgressSpinner from 'primevue/progressspinner';
import Toast from 'primevue/toast';
import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast';
import { onMounted, ref } from 'vue';
import { computed, onMounted, ref } from 'vue';
// Import useConfirm dynamically to avoid the error when the service is not provided
const confirm = useConfirm();
@@ -44,55 +45,23 @@ const mediaTypes = ref([
const selectedMediaType = ref(mediaTypes.value[0]);
const globalFilterValue = ref('');
const loading = ref(false);
const filters = ref({
global: { value: null, matchMode: 'contains' }
});
// Add pagination state
const first = ref(0);
const rows = ref(10);
const totalRecords = ref(0);
const currentPage = ref(1);
// Add computed for total pages
const totalPages = computed(() => {
return Math.ceil(totalRecords.value / rows.value);
});
// Sample data - in a real app, this would come from an API
const mediaFiles = ref([
{
id: 1,
fileName: 'sunset-beach.jpg',
uploadTime: 'Today, 10:30 AM',
fileSize: '2.4 MB',
fileType: 'Image',
thumbnailUrl: 'https://via.placeholder.com/300x225',
iconClass: 'pi-image'
},
{
id: 2,
fileName: 'presentation.pdf',
uploadTime: 'Yesterday, 3:45 PM',
fileSize: '4.8 MB',
fileType: 'PDF',
thumbnailUrl: null,
iconClass: 'pi-file-pdf'
},
{
id: 3,
fileName: 'promo_video.mp4',
uploadTime: 'Aug 28, 2023',
fileSize: '24.8 MB',
fileType: 'Video',
thumbnailUrl: null,
iconClass: 'pi-video'
},
{
id: 4,
fileName: 'report_q3.docx',
uploadTime: 'Aug 25, 2023',
fileSize: '1.2 MB',
fileType: 'Document',
thumbnailUrl: null,
iconClass: 'pi-file'
},
{
id: 5,
fileName: 'podcast_interview.mp3',
uploadTime: 'Aug 20, 2023',
fileSize: '18.5 MB',
fileType: 'Audio',
thumbnailUrl: null,
iconClass: 'pi-volume-up'
}
]);
const mediaFiles = ref([]);
// File upload handling
const onUpload = (event) => {
@@ -109,20 +78,18 @@ const onUpload = (event) => {
// Preview file
const previewFile = (file) => {
// In a real app, this would open a modal with a preview or navigate to a preview page
toast.add({ severity: 'info', summary: 'Preview', detail: `Previewing ${file.fileName}`, life: 3000 });
toast.add({ severity: 'info', summary: 'Preview', detail: `Previewing ${file.name}`, life: 3000 });
};
// Download file
const downloadFile = (file) => {
// In a real app, this would trigger a download
toast.add({ severity: 'info', summary: 'Download', detail: `Downloading ${file.fileName}`, life: 3000 });
toast.add({ severity: 'info', summary: 'Download', detail: `Downloading ${file.name}`, life: 3000 });
};
// Delete file
const confirmDelete = (file) => {
confirm.require({
message: `Are you sure you want to delete ${file.fileName}?`,
message: `Are you sure you want to delete ${file.name}?`,
header: 'Confirmation',
icon: 'pi pi-exclamation-triangle',
acceptClass: 'p-button-danger',
@@ -138,13 +105,13 @@ const confirmDelete = (file) => {
const fetchMediaFiles = async () => {
loading.value = true;
try {
// In a real app, this would be an API call
// const response = await mediaApi.getMediaFiles();
// mediaFiles.value = response.data;
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Using sample data already defined above
const response = await mediaService.getMedias({
page: currentPage.value,
limit: rows.value
});
mediaFiles.value = response.items;
totalRecords.value = response.total;
console.log(totalRecords.value);
} catch (error) {
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to load media files', life: 3000 });
} finally {
@@ -152,6 +119,16 @@ const fetchMediaFiles = async () => {
}
};
// Add page change handler
const onPage = (event) => {
first.value = event.first;
rows.value = event.rows;
currentPage.value = Math.floor(event.first / event.rows) + 1;
console.log(event)
fetchMediaFiles();
};
onMounted(() => {
fetchMediaFiles();
});
@@ -173,6 +150,27 @@ const getFileIcon = (file) => {
if (file.thumbnailUrl) return null;
return `pi ${file.iconClass}`;
};
// Add helper function to format file size
const formatFileSize = (bytes) => {
if (bytes === 0) return '0 B';
const k = BigInt(1024);
const sizes = ['B', 'KB', 'MB', 'GB'];
// Convert input to BigInt
const bytesValue = BigInt(bytes);
// Calculate the appropriate unit
let i = 0;
let size = bytesValue;
while (size >= k && i < sizes.length - 1) {
size = size / k;
i++;
}
// Convert back to number and format with commas
return `${Number(size).toLocaleString('en-US')} ${sizes[i]}`;
};
</script>
<template>
@@ -193,13 +191,22 @@ const getFileIcon = (file) => {
<InputText v-model="globalFilterValue" placeholder="Search media..." class="flex-1" />
</div>
<DataTable v-model:filters="filters" :value="mediaFiles" :paginator="true" :rows="5"
<DataTable v-model:filters="filters" :value="mediaFiles" :paginator="true" v-model:first="first"
v-model:rows="rows" :totalRecords="totalRecords" @page="onPage"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
:rowsPerPageOptions="[5, 10, 25]"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries" :loading="loading"
dataKey="id" :globalFilterFields="['fileName', 'fileType']"
:filters="{ global: { value: globalFilterValue, matchMode: 'contains' } }" stripedRows removableSort
class="p-datatable-sm" responsiveLayout="scroll">
:rows-per-page-options="[10, 25, 50]"
current-page-report-template="Showing {first} to {last} of {totalRecords} entries" :lazy="true"
:show-current-page-report="true">
<template #paginatorLeft>
<div class="flex items-center">
Per Page: {{ rows }}
</div>
</template>
<template #paginatorRight>
<div class="flex items-center">
Page {{ currentPage }} of {{ totalPages }}
</div>
</template>
<template #empty>
<div class="text-center p-4">No media files found.</div>
</template>
@@ -210,28 +217,32 @@ const getFileIcon = (file) => {
</div>
</template>
<Column field="fileName" header="File Name" sortable>
<Column field="name" header="File Name" sortable>
<template #body="{ data }">
<div class="flex items-center">
<div v-if="data.thumbnailUrl" class="flex-shrink-0 h-10 w-10 mr-3">
<img class="h-10 w-10 object-cover rounded" :src="data.thumbnailUrl"
:alt="data.fileName">
<div v-if="data.thumbnail_url" class="flex-shrink-0 h-10 w-10 mr-3">
<img class="h-10 w-10 object-cover rounded" :src="data.thumbnail_url" :alt="data.name">
</div>
<div v-else
class="flex-shrink-0 h-10 w-10 mr-3 bg-gray-100 rounded flex items-center justify-center">
<i :class="getFileIcon(data)" class="text-2xl"></i>
</div>
<div class="text-sm font-medium text-gray-900">{{ data.fileName }}</div>
<div class="text-sm font-medium text-gray-900">{{ data.name }}</div>
</div>
</template>
</Column>
<Column field="uploadTime" header="Upload Time" sortable></Column>
<Column field="fileSize" header="File Size" sortable></Column>
<Column field="upload_time" header="Upload Time" sortable></Column>
<Column field="fileType" header="File Type" sortable>
<Column field="file_size" header="File Size" sortable>
<template #body="{ data }">
<Badge :value="data.fileType" :severity="getBadgeSeverity(data.fileType)" />
{{ formatFileSize(data.file_size) }}
</template>
</Column>
<Column field="media_type" header="File Type" sortable>
<template #body="{ data }">
<Badge :value="data.file_type" :severity="getBadgeSeverity(data.file_type)" />
</template>
<template #filter="{ filterModel, filterCallback }">
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="mediaTypes"

View File

@@ -15,6 +15,9 @@ export default defineConfig({
},
server: {
port: 3000,
open: true
open: true,
proxy: {
'/v1': 'http://localhost:8088',
}
}
});