diff --git a/frontend/admin/bun.lock b/frontend/admin/bun.lock
index 3d7db92..617cd04 100644
--- a/frontend/admin/bun.lock
+++ b/frontend/admin/bun.lock
@@ -18,6 +18,7 @@
"tailwindcss": "^4.0.17",
"tailwindcss-primeui": "^0.6.1",
"vue": "^3.5.13",
+ "vue-icons-plus": "^0.1.8",
"vue-router": "4",
},
"devDependencies": {
@@ -337,6 +338,8 @@
"vue": ["vue@3.5.13", "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", "@vue/runtime-dom": "3.5.13", "@vue/server-renderer": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ=="],
+ "vue-icons-plus": ["vue-icons-plus@0.1.8", "https://registry.npmmirror.com/vue-icons-plus/-/vue-icons-plus-0.1.8.tgz", { "peerDependencies": { "vue": ">=2.7.0" } }, "sha512-Xc4hDsD/oP9waSUf44nSaFBhUPo+QkpKclx0S7//5BRACpXymctbit02epek0VRW6nb81pR486XmxPP/ofm2yQ=="],
+
"vue-router": ["vue-router@4.5.0", "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.0.tgz", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.2.0" } }, "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w=="],
"vue-router/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
diff --git a/frontend/admin/package.json b/frontend/admin/package.json
index 1498bfa..2440646 100644
--- a/frontend/admin/package.json
+++ b/frontend/admin/package.json
@@ -23,6 +23,7 @@
"tailwindcss": "^4.0.17",
"tailwindcss-primeui": "^0.6.1",
"vue": "^3.5.13",
+ "vue-icons-plus": "^0.1.8",
"vue-router": "4"
},
"devDependencies": {
diff --git a/frontend/admin/src/pages/MediaPage.vue b/frontend/admin/src/pages/MediaPage.vue
index c90be2d..5628487 100644
--- a/frontend/admin/src/pages/MediaPage.vue
+++ b/frontend/admin/src/pages/MediaPage.vue
@@ -1,8 +1,8 @@
@@ -190,13 +164,9 @@ const formatDate = (date) => {
-
-
![]()
-
-
-
+
{{ data.name }}
@@ -219,7 +189,7 @@ const formatDate = (date) => {
-
+
= 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]}`;
+}
diff --git a/frontend/admin/src/utils/filetype.js b/frontend/admin/src/utils/filetype.js
new file mode 100644
index 0000000..8528ac0
--- /dev/null
+++ b/frontend/admin/src/utils/filetype.js
@@ -0,0 +1,130 @@
+/**
+ * Get file type category based on MIME type
+ * @param {string} mime - The MIME type of the file
+ * @returns {string} The file type category
+ */
+export function getFileTypeByMime(mime) {
+ if (!mime) return 'unknown';
+
+ const mimeType = mime.toLowerCase();
+
+ // Video files
+ if (mimeType.startsWith('video/')) {
+ return 'video';
+ }
+
+ // Audio files
+ if (mimeType.startsWith('audio/')) {
+ return 'audio';
+ }
+
+ // Image files
+ if (mimeType.startsWith('image/')) {
+ return 'image';
+ }
+
+ // PDF files
+ if (mimeType === 'application/pdf') {
+ return 'pdf';
+ }
+
+ // Document files
+ if ([
+ 'application/msword',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'application/vnd.ms-excel',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'application/vnd.ms-powerpoint',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'application/rtf',
+ 'text/plain',
+ 'text/csv'
+ ].includes(mimeType)) {
+ return 'document';
+ }
+
+ // Compressed files
+ if ([
+ 'application/zip',
+ 'application/x-rar-compressed',
+ 'application/x-7z-compressed',
+ 'application/x-tar',
+ 'application/gzip'
+ ].includes(mimeType)) {
+ return 'compressed';
+ }
+
+ return 'other';
+}
+
+/**
+ * Get Chinese file type category based on MIME type
+ * @param {string} mime - The MIME type of the file
+ * @returns {string} The Chinese file type category
+ */
+export function getFileTypeByMimeCN(mime) {
+ const typeMap = {
+ 'video': '视频',
+ 'audio': '音频',
+ 'image': '图片',
+ 'pdf': 'PDF文档',
+ 'document': '文档',
+ 'compressed': '压缩包',
+ 'other': '其他',
+ 'unknown': '未知'
+ };
+
+ return typeMap[getFileTypeByMime(mime)];
+}
+
+/**
+ * Get file icon component based on file extension
+ * @param {Object} file - The file object containing name property
+ * @param {Object} icons - The icons object containing icon components
+ * @returns {Component} The icon component
+ */
+export function getFileIcon(file, icons) {
+ if (!file?.name) return icons.BsFileEarmark;
+
+ const ext = file.name.split('.').pop()?.toLowerCase();
+
+ // Images
+ if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(ext)) {
+ return icons.BsFileImage;
+ }
+
+ // Videos
+ if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'].includes(ext)) {
+ return icons.BsFilePlayFill;
+ }
+
+ // Audio
+ if (['mp3', 'wav', 'ogg', 'aac', 'm4a'].includes(ext)) {
+ return icons.BsFileMusic;
+ }
+
+ // Documents
+ if (['doc', 'docx'].includes(ext)) {
+ return icons.BsFileWord;
+ }
+ if (['xls', 'xlsx'].includes(ext)) {
+ return icons.BsFileExcel;
+ }
+ if (['ppt', 'pptx'].includes(ext)) {
+ return icons.BsFilePpt;
+ }
+ if (ext === 'pdf') {
+ return icons.BsFilePdf;
+ }
+ if (['txt', 'rtf'].includes(ext)) {
+ return icons.BsFileText;
+ }
+
+ // Archives
+ if (['zip', 'rar', '7z', 'tar', 'gz'].includes(ext)) {
+ return icons.BsFileZip;
+ }
+
+ return icons.BsFileEarmark;
+}
+
diff --git a/frontend/admin/src/utils/http.js b/frontend/admin/src/utils/http.js
deleted file mode 100644
index eea9c6a..0000000
--- a/frontend/admin/src/utils/http.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { router } from '@/router';
-import { useAuthStore } from '@/stores/auth';
-import axios from 'axios';
-
-export const http = axios.create({
- baseURL: import.meta.env.VITE_API_BASE_URL,
-});
-
-http.interceptors.request.use((config) => {
- const authStore = useAuthStore();
- if (authStore.token) {
- config.headers.Authorization = `Bearer ${authStore.token}`;
- }
- return config;
-});
-
-http.interceptors.response.use(
- (response) => response,
- (error) => {
- if (error.response?.status === 401) {
- const authStore = useAuthStore();
- authStore.logout();
- router.push('/login');
- }
- return Promise.reject(error);
- }
-);