feat: update
This commit is contained in:
@@ -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=="],
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup>
|
||||
import { mediaService } from "@/api/mediaService";
|
||||
import dayjs from 'dayjs';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import { formatDate } from "@/utils/date";
|
||||
import { formatFileSize } from "@/utils/filesize";
|
||||
import { getFileIcon, getFileTypeByMimeCN } from "@/utils/filetype";
|
||||
import { InputText } from "primevue";
|
||||
import Badge from "primevue/badge";
|
||||
import Button from "primevue/button";
|
||||
@@ -17,6 +17,19 @@ import { useToast } from "primevue/usetoast";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import {
|
||||
BsFileEarmark,
|
||||
BsFileExcel,
|
||||
BsFileImage,
|
||||
BsFileMusic,
|
||||
BsFilePdf,
|
||||
BsFilePlayFill,
|
||||
BsFilePpt,
|
||||
BsFileText,
|
||||
BsFileWord,
|
||||
BsFileZip
|
||||
} from 'vue-icons-plus/bs';
|
||||
|
||||
// Import useConfirm dynamically to avoid the error when the service is not provided
|
||||
const confirm = useConfirm();
|
||||
const toast = useToast();
|
||||
@@ -27,18 +40,6 @@ const router = useRouter();
|
||||
const openUploadDialog = () => {
|
||||
router.push("/medias/uploads");
|
||||
};
|
||||
|
||||
// Media types for filtering
|
||||
const mediaTypes = ref([
|
||||
{ name: "所有媒体", value: null },
|
||||
{ name: "图片", value: "Image" },
|
||||
{ name: "视频", value: "Video" },
|
||||
{ name: "文档", value: "Document" },
|
||||
{ name: "音频", value: "Audio" },
|
||||
{ name: "PDF", value: "PDF" },
|
||||
]);
|
||||
|
||||
const selectedMediaType = ref(mediaTypes.value[0]);
|
||||
const globalFilterValue = ref("");
|
||||
const loading = ref(false);
|
||||
const filters = ref({
|
||||
@@ -96,52 +97,25 @@ onMounted(() => {
|
||||
fetchMediaFiles();
|
||||
});
|
||||
|
||||
// File type badge severity mapping
|
||||
const getBadgeSeverity = (fileType) => {
|
||||
const map = {
|
||||
Image: "info",
|
||||
PDF: "danger",
|
||||
Video: "warning",
|
||||
Document: "primary",
|
||||
Audio: "success",
|
||||
};
|
||||
return map[fileType] || "info";
|
||||
// Remove getBadgeSeverity function as it's no longer needed
|
||||
|
||||
// Create icons object
|
||||
const icons = {
|
||||
BsFileEarmark,
|
||||
BsFileExcel,
|
||||
BsFileImage,
|
||||
BsFileMusic,
|
||||
BsFilePdf,
|
||||
BsFilePlayFill,
|
||||
BsFilePpt,
|
||||
BsFileText,
|
||||
BsFileWord,
|
||||
BsFileZip
|
||||
};
|
||||
|
||||
// File type icon mapping
|
||||
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", "TB", "PB", "EB", "ZB", "YB"];
|
||||
|
||||
// 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]}`;
|
||||
};
|
||||
|
||||
// Configure dayjs
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
// Add formatDate function
|
||||
const formatDate = (date) => {
|
||||
return dayjs.tz(date, 'Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
|
||||
// Replace getFileIcon function with a simpler version
|
||||
const getFileIconComponent = (file) => {
|
||||
return getFileIcon(file, icons);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -190,13 +164,9 @@ const formatDate = (date) => {
|
||||
<Column field="name" header="文件名">
|
||||
<template #body="{ data }">
|
||||
<div class="flex items-center">
|
||||
<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
|
||||
<div
|
||||
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>
|
||||
<component :is="getFileIconComponent(data)" class="text-xl text-gray-600" />
|
||||
</div>
|
||||
<div class="text-sm font-medium text-gray-900">{{ data.name }}</div>
|
||||
</div>
|
||||
@@ -219,7 +189,7 @@ const formatDate = (date) => {
|
||||
|
||||
<Column field="media_type" header="文件类型">
|
||||
<template #body="{ data }">
|
||||
<Badge :value="data.file_type" :severity="getBadgeSeverity(data.file_type)" />
|
||||
<Badge :value="getFileTypeByMimeCN(data.mime_type)" />
|
||||
</template>
|
||||
<template #filter="{ filterModel, filterCallback }">
|
||||
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="mediaTypes"
|
||||
|
||||
16
frontend/admin/src/utils/date.js
Normal file
16
frontend/admin/src/utils/date.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import dayjs from 'dayjs';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
|
||||
// Configure dayjs
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
/**
|
||||
* Format date to locale string with timezone
|
||||
* @param {string|Date} date - The date to format
|
||||
* @returns {string} Formatted date string
|
||||
*/
|
||||
export function formatDate(date) {
|
||||
return dayjs.tz(date, 'Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
24
frontend/admin/src/utils/filesize.js
Normal file
24
frontend/admin/src/utils/filesize.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Format file size to human readable string
|
||||
* @param {number} bytes - The file size in bytes
|
||||
* @returns {string} Formatted file size with unit
|
||||
*/
|
||||
export function formatFileSize(bytes) {
|
||||
if (bytes === 0) return "0 B";
|
||||
const k = BigInt(1024);
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
|
||||
// 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]}`;
|
||||
}
|
||||
130
frontend/admin/src/utils/filetype.js
Normal file
130
frontend/admin/src/utils/filetype.js
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user