feat: update page list show
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
52
frontend/admin/src/api/get_medias.json
Normal file
52
frontend/admin/src/api/get_medias.json
Normal 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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
9
frontend/admin/src/api/mediaService.js
Normal file
9
frontend/admin/src/api/mediaService.js
Normal 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 }
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -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"
|
||||
|
||||
@@ -15,6 +15,9 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
open: true
|
||||
open: true,
|
||||
proxy: {
|
||||
'/v1': 'http://localhost:8088',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user