feat: update
This commit is contained in:
@@ -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=="],
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
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