feat: update page list show
This commit is contained in:
@@ -64,8 +64,8 @@ func (m *mediasModel) countByCondition(ctx context.Context, expr BoolExpression)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *mediasModel) List(ctx context.Context, pagination *requests.Pagination) (*requests.Pager, error) {
|
func (m *mediasModel) List(ctx context.Context, pagination *requests.Pagination) (*requests.Pager, error) {
|
||||||
limit := pagination.Limit
|
limit := pagination.GetLimit()
|
||||||
offset := pagination.Offset()
|
offset := pagination.GetOffset()
|
||||||
|
|
||||||
tbl := table.Medias
|
tbl := table.Medias
|
||||||
stmt := tbl.
|
stmt := tbl.
|
||||||
@@ -89,8 +89,8 @@ func (m *mediasModel) List(ctx context.Context, pagination *requests.Pagination)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert model.Medias to MediaItem
|
// Convert model.Medias to MediaItem
|
||||||
mediaItems := lo.Map(medias, func(media model.Medias, _ int) MediaItem {
|
mediaItems := lo.Map(medias, func(media model.Medias, _ int) *MediaItem {
|
||||||
return MediaItem{
|
return &MediaItem{
|
||||||
ID: media.ID,
|
ID: media.ID,
|
||||||
Name: media.Name,
|
Name: media.Name,
|
||||||
UploadTime: media.CreatedAt.Format("2006-01-02 15:04:05"),
|
UploadTime: media.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||||
@@ -108,6 +108,19 @@ func (m *mediasModel) List(ctx context.Context, pagination *requests.Pagination)
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mediasModel) BatchCreate(ctx context.Context, models []*model.Medias) error {
|
||||||
|
stmt := table.Medias.INSERT(table.Medias.MutableColumns).MODELS(models)
|
||||||
|
m.log.Infof("sql: %s", stmt.DebugSql())
|
||||||
|
|
||||||
|
if _, err := stmt.ExecContext(ctx, db); err != nil {
|
||||||
|
m.log.Errorf("error creating media item: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.log.Infof("media item created successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mediasModel) Create(ctx context.Context, model *model.Medias) error {
|
func (m *mediasModel) Create(ctx context.Context, model *model.Medias) error {
|
||||||
stmt := table.Medias.INSERT(table.Medias.MutableColumns).MODEL(model)
|
stmt := table.Medias.INSERT(table.Medias.MutableColumns).MODEL(model)
|
||||||
m.log.Infof("sql: %s", stmt.DebugSql())
|
m.log.Infof("sql: %s", stmt.DebugSql())
|
||||||
@@ -123,9 +136,7 @@ func (m *mediasModel) Create(ctx context.Context, model *model.Medias) error {
|
|||||||
|
|
||||||
func (m *mediasModel) ConvertFileTypeByMimeType(mimeType string) MediaType {
|
func (m *mediasModel) ConvertFileTypeByMimeType(mimeType string) MediaType {
|
||||||
switch mimeType {
|
switch mimeType {
|
||||||
case "image/jpeg":
|
case "image/jpeg", "image/jpg", "image/png":
|
||||||
case "image/jpg":
|
|
||||||
case "image/png":
|
|
||||||
return MediaTypeImage
|
return MediaTypeImage
|
||||||
case "video/mp4":
|
case "video/mp4":
|
||||||
return MediaTypeVideo
|
return MediaTypeVideo
|
||||||
@@ -133,19 +144,19 @@ func (m *mediasModel) ConvertFileTypeByMimeType(mimeType string) MediaType {
|
|||||||
return MediaTypeAudio
|
return MediaTypeAudio
|
||||||
case "application/pdf":
|
case "application/pdf":
|
||||||
return MediaTypePDF
|
return MediaTypePDF
|
||||||
case "application/msword":
|
case "application/msword",
|
||||||
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
case "application/vnd.ms-excel":
|
"application/vnd.ms-excel",
|
||||||
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
case "application/vnd.ms-powerpoint":
|
"application/vnd.ms-powerpoint",
|
||||||
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
||||||
return MediaTypeDocument
|
return MediaTypeDocument
|
||||||
case "application/rar":
|
case "application/rar",
|
||||||
case "application/x-rar-compressed":
|
"application/x-rar-compressed",
|
||||||
case "application/x-zip-compressed":
|
"application/x-zip-compressed",
|
||||||
case "application/x-zip":
|
"application/x-zip",
|
||||||
case "application/zip":
|
"application/zip",
|
||||||
case "application/x-7z-compressed":
|
"application/x-7z-compressed":
|
||||||
return MediaTypeArchive
|
return MediaTypeArchive
|
||||||
}
|
}
|
||||||
return MediaTypeUnknown
|
return MediaTypeUnknown
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package models
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -54,6 +56,60 @@ func (s *MediasTestSuite) Test_countByCondition() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MediasTestSuite) Test_BatchCreate() {
|
||||||
|
Convey("Create", s.T(), func() {
|
||||||
|
Convey("valid media", func() {
|
||||||
|
database.Truncate(context.Background(), db, table.Medias.TableName())
|
||||||
|
|
||||||
|
models := []*model.Medias{
|
||||||
|
{
|
||||||
|
Name: "test 01",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
MimeType: "video/mp4",
|
||||||
|
Size: rand.Int63n(math.MaxInt64), // Random size
|
||||||
|
Path: "path/to/media.mp4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test 02",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
MimeType: "audio/mp3",
|
||||||
|
Size: rand.Int63n(math.MaxInt64), // Random size
|
||||||
|
Path: "path/to/media.mp3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test 03",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
MimeType: "application/pdf",
|
||||||
|
Size: rand.Int63n(math.MaxInt64), // Random size
|
||||||
|
Path: "path/to/media.pdf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test 04",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
MimeType: "image/jpeg",
|
||||||
|
Size: rand.Int63n(math.MaxInt64), // Random size
|
||||||
|
Path: "path/to/media.jpeg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test 05",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
MimeType: "application/zip",
|
||||||
|
Size: rand.Int63n(math.MaxInt64), // Random size
|
||||||
|
Path: "path/to/media.zip",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 10
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
err := Medias.BatchCreate(context.Background(), models)
|
||||||
|
Convey("Create should not return an error: "+fmt.Sprintf("%d", i), func() {
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MediasTestSuite) Test_Create() {
|
func (s *MediasTestSuite) Test_Create() {
|
||||||
Convey("Create", s.T(), func() {
|
Convey("Create", s.T(), func() {
|
||||||
Convey("valid media", func() {
|
Convey("valid media", func() {
|
||||||
@@ -132,3 +188,26 @@ func (s *MediasTestSuite) Test_Page() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test ConvertFileTypeByMimeType
|
||||||
|
func (s *MediasTestSuite) Test_ConvertFileTypeByMimeType() {
|
||||||
|
Convey("ConvertFileTypeByMimeType", s.T(), func() {
|
||||||
|
Convey("image", func() {
|
||||||
|
mimeType := "image/jpeg"
|
||||||
|
fileType := Medias.ConvertFileTypeByMimeType(mimeType)
|
||||||
|
So(fileType, ShouldEqual, MediaTypeImage)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("video", func() {
|
||||||
|
mimeType := "video/mp4"
|
||||||
|
fileType := Medias.ConvertFileTypeByMimeType(mimeType)
|
||||||
|
So(fileType, ShouldEqual, MediaTypeVideo)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("invalid mime type", func() {
|
||||||
|
mimeType := "invalid/type"
|
||||||
|
fileType := Medias.ConvertFileTypeByMimeType(mimeType)
|
||||||
|
So(fileType, ShouldEqual, MediaTypeUnknown)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -118,8 +118,8 @@ func (m *postsModel) countByCondition(ctx context.Context, expr BoolExpression)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *postsModel) List(ctx context.Context, pagination *requests.Pagination, cond BoolExpression) (*requests.Pager, error) {
|
func (m *postsModel) List(ctx context.Context, pagination *requests.Pagination, cond BoolExpression) (*requests.Pager, error) {
|
||||||
limit := pagination.Limit
|
limit := pagination.GetLimit()
|
||||||
offset := pagination.Offset()
|
offset := pagination.GetOffset()
|
||||||
|
|
||||||
tbl := table.Posts
|
tbl := table.Posts
|
||||||
stmt := tbl.
|
stmt := tbl.
|
||||||
|
|||||||
@@ -13,10 +13,17 @@ type Pagination struct {
|
|||||||
Limit int64 `json:"limit" form:"limit" query:"limit"`
|
Limit int64 `json:"limit" form:"limit" query:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (filter *Pagination) Offset() int64 {
|
func (filter *Pagination) GetOffset() int64 {
|
||||||
return (filter.Page - 1) * filter.Limit
|
return (filter.Page - 1) * filter.Limit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (filter *Pagination) GetLimit() int64 {
|
||||||
|
if filter.Limit <= 0 {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
return filter.Limit
|
||||||
|
}
|
||||||
|
|
||||||
func (filter *Pagination) Format() *Pagination {
|
func (filter *Pagination) Format() *Pagination {
|
||||||
if filter.Page <= 0 {
|
if filter.Page <= 0 {
|
||||||
filter.Page = 1
|
filter.Page = 1
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"quyun/app/errorx"
|
"quyun/app/errorx"
|
||||||
appHttp "quyun/app/http"
|
appHttp "quyun/app/http"
|
||||||
"quyun/app/jobs"
|
"quyun/app/jobs"
|
||||||
|
"quyun/app/models"
|
||||||
"quyun/app/service"
|
"quyun/app/service"
|
||||||
_ "quyun/docs"
|
_ "quyun/docs"
|
||||||
"quyun/providers/ali"
|
"quyun/providers/ali"
|
||||||
@@ -47,6 +48,7 @@ func Command() atom.Option {
|
|||||||
defaultProviders().
|
defaultProviders().
|
||||||
With(
|
With(
|
||||||
jobs.Provide,
|
jobs.Provide,
|
||||||
|
models.Provide,
|
||||||
).
|
).
|
||||||
WithProviders(
|
WithProviders(
|
||||||
appHttp.Providers(),
|
appHttp.Providers(),
|
||||||
@@ -68,7 +70,7 @@ type Service struct {
|
|||||||
|
|
||||||
func Serve(cmd *cobra.Command, args []string) error {
|
func Serve(cmd *cobra.Command, args []string) error {
|
||||||
return container.Container.Invoke(func(ctx context.Context, svc Service) error {
|
return container.Container.Invoke(func(ctx context.Context, svc Service) error {
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
log.SetFormatter(&log.TextFormatter{})
|
||||||
|
|
||||||
if svc.App.Mode == app.AppModeDevelopment {
|
if svc.App.Mode == app.AppModeDevelopment {
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|||||||
@@ -32,3 +32,7 @@ Content-Type: application/json
|
|||||||
### get oss token
|
### get oss token
|
||||||
GET {{host}}/v1/admin/uploads/token HTTP/1.1
|
GET {{host}}/v1/admin/uploads/token HTTP/1.1
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
|
### get medias
|
||||||
|
GET {{host}}/v1/admin/medias HTTP/1.1
|
||||||
|
Content-Type: application/json
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<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">
|
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
||||||
<meta name="description" content="趣云内容管理系统后台">
|
<meta name="description" content="趣云内容管理系统后台">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const navItems = ref([
|
|||||||
<Menubar :model="navItems" class="!rounded-none">
|
<Menubar :model="navItems" class="!rounded-none">
|
||||||
<template #start>
|
<template #start>
|
||||||
<div class="flex items-center pr-4">
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #end>
|
<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
|
// Create axios instance with default config
|
||||||
const httpClient = axios.create({
|
const httpClient = axios.create({
|
||||||
baseURL: '/api',
|
baseURL: '/v1',
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'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>
|
<script setup>
|
||||||
|
import { mediaService } from '@/api/mediaService';
|
||||||
import { InputText } from 'primevue';
|
import { InputText } from 'primevue';
|
||||||
import Badge from 'primevue/badge';
|
import Badge from 'primevue/badge';
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
@@ -12,7 +13,7 @@ import ProgressSpinner from 'primevue/progressspinner';
|
|||||||
import Toast from 'primevue/toast';
|
import Toast from 'primevue/toast';
|
||||||
import { useConfirm } from 'primevue/useconfirm';
|
import { useConfirm } from 'primevue/useconfirm';
|
||||||
import { useToast } from 'primevue/usetoast';
|
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
|
// Import useConfirm dynamically to avoid the error when the service is not provided
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
@@ -44,55 +45,23 @@ const mediaTypes = ref([
|
|||||||
const selectedMediaType = ref(mediaTypes.value[0]);
|
const selectedMediaType = ref(mediaTypes.value[0]);
|
||||||
const globalFilterValue = ref('');
|
const globalFilterValue = ref('');
|
||||||
const loading = ref(false);
|
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
|
// Sample data - in a real app, this would come from an API
|
||||||
const mediaFiles = ref([
|
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'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
// File upload handling
|
// File upload handling
|
||||||
const onUpload = (event) => {
|
const onUpload = (event) => {
|
||||||
@@ -109,20 +78,18 @@ const onUpload = (event) => {
|
|||||||
|
|
||||||
// Preview file
|
// Preview file
|
||||||
const previewFile = (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.name}`, life: 3000 });
|
||||||
toast.add({ severity: 'info', summary: 'Preview', detail: `Previewing ${file.fileName}`, life: 3000 });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Download file
|
// Download file
|
||||||
const downloadFile = (file) => {
|
const downloadFile = (file) => {
|
||||||
// In a real app, this would trigger a download
|
toast.add({ severity: 'info', summary: 'Download', detail: `Downloading ${file.name}`, life: 3000 });
|
||||||
toast.add({ severity: 'info', summary: 'Download', detail: `Downloading ${file.fileName}`, life: 3000 });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete file
|
// Delete file
|
||||||
const confirmDelete = (file) => {
|
const confirmDelete = (file) => {
|
||||||
confirm.require({
|
confirm.require({
|
||||||
message: `Are you sure you want to delete ${file.fileName}?`,
|
message: `Are you sure you want to delete ${file.name}?`,
|
||||||
header: 'Confirmation',
|
header: 'Confirmation',
|
||||||
icon: 'pi pi-exclamation-triangle',
|
icon: 'pi pi-exclamation-triangle',
|
||||||
acceptClass: 'p-button-danger',
|
acceptClass: 'p-button-danger',
|
||||||
@@ -138,13 +105,13 @@ const confirmDelete = (file) => {
|
|||||||
const fetchMediaFiles = async () => {
|
const fetchMediaFiles = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
// In a real app, this would be an API call
|
const response = await mediaService.getMedias({
|
||||||
// const response = await mediaApi.getMediaFiles();
|
page: currentPage.value,
|
||||||
// mediaFiles.value = response.data;
|
limit: rows.value
|
||||||
|
});
|
||||||
// Simulate API delay
|
mediaFiles.value = response.items;
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
totalRecords.value = response.total;
|
||||||
// Using sample data already defined above
|
console.log(totalRecords.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to load media files', life: 3000 });
|
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to load media files', life: 3000 });
|
||||||
} finally {
|
} 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(() => {
|
onMounted(() => {
|
||||||
fetchMediaFiles();
|
fetchMediaFiles();
|
||||||
});
|
});
|
||||||
@@ -173,6 +150,27 @@ const getFileIcon = (file) => {
|
|||||||
if (file.thumbnailUrl) return null;
|
if (file.thumbnailUrl) return null;
|
||||||
return `pi ${file.iconClass}`;
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -193,13 +191,22 @@ const getFileIcon = (file) => {
|
|||||||
<InputText v-model="globalFilterValue" placeholder="Search media..." class="flex-1" />
|
<InputText v-model="globalFilterValue" placeholder="Search media..." class="flex-1" />
|
||||||
</div>
|
</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"
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
||||||
:rowsPerPageOptions="[5, 10, 25]"
|
:rows-per-page-options="[10, 25, 50]"
|
||||||
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries" :loading="loading"
|
current-page-report-template="Showing {first} to {last} of {totalRecords} entries" :lazy="true"
|
||||||
dataKey="id" :globalFilterFields="['fileName', 'fileType']"
|
:show-current-page-report="true">
|
||||||
:filters="{ global: { value: globalFilterValue, matchMode: 'contains' } }" stripedRows removableSort
|
<template #paginatorLeft>
|
||||||
class="p-datatable-sm" responsiveLayout="scroll">
|
<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>
|
<template #empty>
|
||||||
<div class="text-center p-4">No media files found.</div>
|
<div class="text-center p-4">No media files found.</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -210,28 +217,32 @@ const getFileIcon = (file) => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<Column field="fileName" header="File Name" sortable>
|
<Column field="name" header="File Name" sortable>
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div v-if="data.thumbnailUrl" class="flex-shrink-0 h-10 w-10 mr-3">
|
<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.thumbnailUrl"
|
<img class="h-10 w-10 object-cover rounded" :src="data.thumbnail_url" :alt="data.name">
|
||||||
:alt="data.fileName">
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else
|
<div v-else
|
||||||
class="flex-shrink-0 h-10 w-10 mr-3 bg-gray-100 rounded flex items-center justify-center">
|
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>
|
<i :class="getFileIcon(data)" class="text-2xl"></i>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<Column field="uploadTime" header="Upload Time" sortable></Column>
|
<Column field="upload_time" header="Upload Time" sortable></Column>
|
||||||
<Column field="fileSize" header="File Size" sortable></Column>
|
|
||||||
|
|
||||||
<Column field="fileType" header="File Type" sortable>
|
<Column field="file_size" header="File Size" sortable>
|
||||||
<template #body="{ data }">
|
<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>
|
||||||
<template #filter="{ filterModel, filterCallback }">
|
<template #filter="{ filterModel, filterCallback }">
|
||||||
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="mediaTypes"
|
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="mediaTypes"
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
open: true
|
open: true,
|
||||||
|
proxy: {
|
||||||
|
'/v1': 'http://localhost:8088',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user