diff --git a/src/apis/system/file.ts b/src/apis/system/file.ts index 56d7012aacd92a1a14598cfc118dd0c00a060664..635c0fdb9c4a8e6a965c6f68c9caf195f8540c78 100644 --- a/src/apis/system/file.ts +++ b/src/apis/system/file.ts @@ -1,3 +1,4 @@ +import type { AxiosRequestConfig } from 'axios' import type * as T from './type' import http from '@/utils/http' @@ -7,8 +8,18 @@ const BASE_URL = '/system/file' const RECYCLE_URL = `${BASE_URL}/recycle` /** @desc 上传文件 */ -export function uploadFile(data: FormData) { - return http.post(`${BASE_URL}/upload`, data) +export function uploadFile(data: FormData, config?: AxiosRequestConfig) { + return http.post(`${BASE_URL}/upload`, data, config) +} + +/** @desc 查询默认存储上传配置 */ +export function getDefaultUploadConfig() { + return http.get(`${BASE_URL}/upload/config/default`) +} + +/** @desc 查询单文件上传进度 */ +export function getUploadProgress(uploadTaskId: string) { + return http.get(`${BASE_URL}/upload/progress/${uploadTaskId}`) } /** @desc 查询文件列表 */ diff --git a/src/apis/system/type.ts b/src/apis/system/type.ts index 3aecca27bf648fb94f9920719a25bc763982082f..b988a605021a80494adaab4ca4f3ad9571a229be 100644 --- a/src/apis/system/type.ts +++ b/src/apis/system/type.ts @@ -249,6 +249,32 @@ export interface FileStatisticsResp { export interface FileDirCalcSizeResp { size: number } +export interface FileUploadResp { + id: string + url: string + thUrl: string + metadata: Record +} +export type StorageType = 1 | 2 +export interface FileUploadConfigResp { + storageId: string | number + storageName: string + storageCode: string + storageType: StorageType + multipartUploadThreshold: number + multipartUploadPartSize: number + multipartTempDir?: string | null +} +export interface FileUploadProgressResp { + uploadTaskId: string + status: 'INIT' | 'UPLOADING' | 'FINALIZING' | 'COMPLETED' | 'FAILED' | 'NOT_FOUND' + percentage: number + bytesRead: number + totalBytes: number + fileId?: string + url?: string + message?: string +} export interface FileQuery { originalName?: string type?: string @@ -269,6 +295,9 @@ export interface StorageResp { endpoint: string bucketName: string domain: string + multipartUploadThreshold?: number | null + multipartUploadPartSize?: number | null + multipartTempDir?: string | null recycleBinEnabled: boolean recycleBinPath: string description: string diff --git a/src/components/FilePreview/index.vue b/src/components/FilePreview/index.vue index 8de9da98e9d6913b4dfd6fbe8e805da751ee6d6a..f73bb1ac15e0c4147d9d4a233dc2b540cd0a2110 100644 --- a/src/components/FilePreview/index.vue +++ b/src/components/FilePreview/index.vue @@ -69,7 +69,7 @@ const filePreview = reactive({ }) // 弹框标题 const modalTitle = computed(() => { - const { fileName, fileType } = filePreview.fileInfo || {} + const { fileName } = filePreview.fileInfo || {} // fileName 已经包含扩展名,直接显示 return fileName || '文件预览' }) diff --git a/src/components/MultipartUpload/index.vue b/src/components/MultipartUpload/index.vue index 55d6a84e5dd8dd8e834c43a7c88da8c4b72e9cdd..e0d6e82f030a8147b0b910d769ded1b2e33823c3 100644 --- a/src/components/MultipartUpload/index.vue +++ b/src/components/MultipartUpload/index.vue @@ -21,10 +21,21 @@ 清空 +
+
默认存储上传策略
+ +
+
类型{{ storageTypeLabel }}
+
分片阈值{{ thresholdMb }} MB
+
分片大小{{ partSizeMb }} MB
+
本地分片临时目录{{ defaultUploadConfig.multipartTempDir || '-' }}
+
+
+
支持拖拽文件到此区域上传(文件夹请使用"选择文件夹"按钮)
- 提示:拖拽上传时,所有文件将上传到根目录 + 系统会根据默认存储分片阈值自动选择单文件上传或分片上传
@@ -33,6 +44,7 @@ :data="fileTasks" :columns="columns" row-key="uid" + :scroll="{ y: '100%' }" :pagination="pagination" style="height: 100%; background: transparent;" > @@ -47,7 +59,7 @@ + + diff --git a/src/views/system/file/main/FileAsideStatistics.vue b/src/views/system/file/main/FileAsideStatistics.vue index 1d6ee8533a8dfb05a3dc5e52e176b4643704b5eb..fdfd165074b005ac966cd148db03b8a200df38c6 100644 --- a/src/views/system/file/main/FileAsideStatistics.vue +++ b/src/views/system/file/main/FileAsideStatistics.vue @@ -11,7 +11,20 @@
- + +
+
+ + {{ item.name || '未知类型' }} +
+
@@ -20,7 +33,7 @@ import VCharts from 'vue-echarts' import { use } from 'echarts/core' import { PieChart } from 'echarts/charts' -import { LegendComponent, TitleComponent, TooltipComponent } from 'echarts/components' +import { TitleComponent, TooltipComponent } from 'echarts/components' import { CanvasRenderer } from 'echarts/renderers' import { FileTypeList } from '@/constant/file' import { useChart } from '@/hooks' @@ -28,7 +41,7 @@ import { type FileStatisticsResp, getFileStatistics } from '@/apis/system' import { formatFileSize } from '@/utils' import mittBus from '@/utils/mitt' -use([TitleComponent, TooltipComponent, LegendComponent, PieChart, CanvasRenderer]) +use([TitleComponent, TooltipComponent, PieChart, CanvasRenderer]) const totalData = ref({ type: '', @@ -37,42 +50,71 @@ const totalData = ref({ unit: '', data: [], }) -const chartData = ref>([]) +const chartData = ref>([]) const statisticValueStyle = { 'color': '#5856D6', 'font-size': '18px' } +const chartHeight = 135 +const chartColors = ['#165DFF', '#36CFC9', '#F7BA1E', '#722ED1', '#00B42A', '#F53F3F', '#86909C', '#FF7D00'] +const hiddenLegendMap = ref>({}) +const legendItems = computed(() => { + return chartData.value.map((item, index) => ({ + ...item, + color: chartColors[index % chartColors.length], + active: !hiddenLegendMap.value[item.key], + })) +}) +const pieData = computed(() => { + return legendItems.value + .filter((item) => item.active) + .map((item) => ({ + name: item.name, + value: item.value, + size: item.size, + itemStyle: { + color: item.color, + }, + })) +}) + +const toggleLegend = (key: string) => { + const nextHiddenMap = { + ...hiddenLegendMap.value, + [key]: !hiddenLegendMap.value[key], + } + const visibleCount = chartData.value.filter((item) => !nextHiddenMap[item.key]).length + // 至少保留一个分类显示,避免整张图被全部隐藏。 + if (visibleCount <= 0) { + return + } + hiddenLegendMap.value = nextHiddenMap +} + const { chartOption } = useChart(() => { return { - grid: { - left: 0, - right: 0, - top: 0, - bottom: 0, - }, - legend: { - show: true, - bottom: -5, - icon: 'circle', - itemWidth: 6, - itemHeight: 6, - textStyle: { - color: '#4E5969', - }, - }, + color: chartColors, tooltip: { show: true, + appendToBody: true, + confine: false, + extraCssText: 'z-index: 9999;', formatter(params) { - return `总计:${params.value}
${params.data.size}` + const name = params?.name || '未知类型' + const count = params?.value ?? 0 + const size = params?.data?.size || '-' + const percent = typeof params?.percent === 'number' ? `${params.percent}%` : '-' + return `类型:${name}
数量:${count}
占比:${percent}
容量:${size}` }, }, series: [ { type: 'pie', - radius: ['40%', '70%'], + radius: ['42%', '68%'], + center: ['50%', '50%'], avoidLabelOverlap: true, label: { show: false, position: 'center', }, - data: chartData.value, + data: pieData.value, }, ], } @@ -83,6 +125,7 @@ const getStatisticsData = async () => { try { loading.value = true chartData.value = [] + hiddenLegendMap.value = {} const { data: resData } = await getFileStatistics() const formatSize = formatFileSize(resData.size).split(' ') totalData.value = { @@ -92,9 +135,10 @@ const getStatisticsData = async () => { unit: formatSize[1], data: [], } - resData.data?.forEach((fs: FileStatisticsResp) => { + resData.data?.forEach((fs: FileStatisticsResp, index: number) => { const matchedItem = FileTypeList.find((item) => item.value === fs.type) chartData.value.unshift({ + key: fs.type || `unknown-${index}`, name: matchedItem ? matchedItem.name : '', value: fs.number, size: formatFileSize(fs.size), @@ -131,6 +175,45 @@ onMounted(() => { background-color: var(--color-bg-1); } +.chart-legend { + margin-top: 10px; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px 10px; +} + +.chart-legend-item { + display: flex; + align-items: center; + color: var(--color-text-2); + font-size: 12px; + line-height: 18px; + min-width: 0; + cursor: pointer; + user-select: none; + transition: opacity 0.2s ease; +} + +.chart-legend-item.is-inactive { + opacity: 0.45; +} + +.chart-legend-dot { + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 6px; + flex: 0 0 auto; +} + +.chart-legend-name { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + :deep(.arco-divider-horizontal) { margin: 20px 0 0 0; } diff --git a/src/views/system/file/main/FileMain/index.vue b/src/views/system/file/main/FileMain/index.vue index 8cf677d8f571c4338f6b6c7b6c85d1c856152f28..39a30bc8292497b07390118aa1af4067d8bb48ab 100644 --- a/src/views/system/file/main/FileMain/index.vue +++ b/src/views/system/file/main/FileMain/index.vue @@ -12,41 +12,22 @@ - - - - - 上传文件 - -