feat: update

This commit is contained in:
yanghao05
2025-04-18 17:41:31 +08:00
parent 62dd5899e0
commit 4165d440ed
7 changed files with 210 additions and 93 deletions

View File

@@ -18,6 +18,7 @@
"tailwindcss": "^4.0.17", "tailwindcss": "^4.0.17",
"tailwindcss-primeui": "^0.6.1", "tailwindcss-primeui": "^0.6.1",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-icons-plus": "^0.1.8",
"vue-router": "4", "vue-router": "4",
}, },
"devDependencies": { "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": ["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-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=="], "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=="],

View File

@@ -23,6 +23,7 @@
"tailwindcss": "^4.0.17", "tailwindcss": "^4.0.17",
"tailwindcss-primeui": "^0.6.1", "tailwindcss-primeui": "^0.6.1",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-icons-plus": "^0.1.8",
"vue-router": "4" "vue-router": "4"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,8 +1,8 @@
<script setup> <script setup>
import { mediaService } from "@/api/mediaService"; import { mediaService } from "@/api/mediaService";
import dayjs from 'dayjs'; import { formatDate } from "@/utils/date";
import timezone from 'dayjs/plugin/timezone'; import { formatFileSize } from "@/utils/filesize";
import utc from 'dayjs/plugin/utc'; import { getFileIcon, getFileTypeByMimeCN } from "@/utils/filetype";
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";
@@ -17,6 +17,19 @@ import { useToast } from "primevue/usetoast";
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router"; 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 // Import useConfirm dynamically to avoid the error when the service is not provided
const confirm = useConfirm(); const confirm = useConfirm();
const toast = useToast(); const toast = useToast();
@@ -27,18 +40,6 @@ const router = useRouter();
const openUploadDialog = () => { const openUploadDialog = () => {
router.push("/medias/uploads"); 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 globalFilterValue = ref("");
const loading = ref(false); const loading = ref(false);
const filters = ref({ const filters = ref({
@@ -96,52 +97,25 @@ onMounted(() => {
fetchMediaFiles(); fetchMediaFiles();
}); });
// File type badge severity mapping // Remove getBadgeSeverity function as it's no longer needed
const getBadgeSeverity = (fileType) => {
const map = { // Create icons object
Image: "info", const icons = {
PDF: "danger", BsFileEarmark,
Video: "warning", BsFileExcel,
Document: "primary", BsFileImage,
Audio: "success", BsFileMusic,
}; BsFilePdf,
return map[fileType] || "info"; BsFilePlayFill,
BsFilePpt,
BsFileText,
BsFileWord,
BsFileZip
}; };
// File type icon mapping // Replace getFileIcon function with a simpler version
const getFileIcon = (file) => { const getFileIconComponent = (file) => {
if (file.thumbnailUrl) return null; return getFileIcon(file, icons);
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');
}; };
</script> </script>
@@ -190,13 +164,9 @@ const formatDate = (date) => {
<Column field="name" header="文件名"> <Column field="name" header="文件名">
<template #body="{ data }"> <template #body="{ data }">
<div class="flex items-center"> <div class="flex items-center">
<div v-if="data.thumbnail_url" class="flex-shrink-0 h-10 w-10 mr-3"> <div
<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"> 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>
<div class="text-sm font-medium text-gray-900">{{ data.name }}</div> <div class="text-sm font-medium text-gray-900">{{ data.name }}</div>
</div> </div>
@@ -219,7 +189,7 @@ const formatDate = (date) => {
<Column field="media_type" header="文件类型"> <Column field="media_type" header="文件类型">
<template #body="{ data }"> <template #body="{ data }">
<Badge :value="data.file_type" :severity="getBadgeSeverity(data.file_type)" /> <Badge :value="getFileTypeByMimeCN(data.mime_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"

View 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');
}

View 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]}`;
}

View 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;
}

View File

@@ -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);
}
);