feat: upgrade admin file workspace

This commit is contained in:
Lan
2026-06-03 04:01:28 +08:00
parent 3b1a510537
commit b97f2cb86b
6 changed files with 739 additions and 187 deletions
+178 -15
View File
@@ -2,11 +2,36 @@ import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { FileService } from '@/services'
import { useAlertStore } from '@/stores/alertStore'
import type { AdminFileViewItem, FileEditForm, FileListItem } from '@/types'
import type {
AdminFileListParams,
AdminFileStatusFilter,
AdminFileSummary,
AdminFileTypeFilter,
AdminFileViewItem,
FileEditForm,
FileListItem
} from '@/types'
import { copyToClipboard } from '@/utils/clipboard'
import { formatFileSize, formatTimestamp, getErrorMessage } from '@/utils/common'
const TEXT_PREVIEW_THRESHOLD = 30
const emptySummary = (): AdminFileSummary => ({
totalFiles: 0,
activeCount: 0,
expiredCount: 0,
textCount: 0,
fileCount: 0,
chunkedCount: 0,
storageUsed: 0,
usedCount: 0
})
const normalizeCount = (value: number | string | null | undefined) => Number(value || 0)
const isExpiredByDate = (value: string | null | undefined) => {
if (!value) return false
const timestamp = new Date(value).getTime()
return Number.isFinite(timestamp) && timestamp < Date.now()
}
export function useAdminFiles() {
const { t } = useI18n()
@@ -14,11 +39,18 @@ export function useAdminFiles() {
const tableData = ref<AdminFileViewItem[]>([])
const hasLoadError = ref(false)
const params = ref({
const isLoading = ref(false)
const isSaving = ref(false)
const summary = ref<AdminFileSummary>(emptySummary())
const params = ref<AdminFileListParams & { total: number }>({
page: 1,
size: 10,
total: 0,
keyword: ''
keyword: '',
status: 'all',
type: 'all',
sortBy: 'created_at',
sortOrder: 'desc'
})
const showEditModal = ref(false)
@@ -33,17 +65,98 @@ export function useAdminFiles() {
const showTextPreview = ref(false)
const previewText = ref('')
const totalPages = computed(() => Math.ceil(params.value.total / params.value.size))
const totalPages = computed(() => Math.max(Math.ceil(params.value.total / params.value.size), 1))
const storageUsedText = computed(() => formatFileSize(summary.value.storageUsed))
const createFileViewItem = (file: FileListItem): AdminFileViewItem => ({
...file,
displaySize: formatFileSize(file.size),
displayExpiredAt: file.expired_at
? formatTimestamp(file.expired_at)
: t('send.expiration.units.forever'),
canPreviewText: Boolean(file.text && file.text.length > TEXT_PREVIEW_THRESHOLD)
const requestParams = computed<AdminFileListParams>(() => ({
page: params.value.page,
size: params.value.size,
keyword: params.value.keyword,
status: params.value.status === 'all' ? undefined : params.value.status,
type: params.value.type === 'all' ? undefined : params.value.type,
sortBy: params.value.sortBy,
sortOrder: params.value.sortOrder
}))
const hasActiveFilters = computed(
() =>
Boolean(params.value.keyword?.trim()) ||
params.value.status !== 'all' ||
params.value.type !== 'all'
)
const inferIsText = (file: FileListItem) => {
if (typeof file.isText === 'boolean') return file.isText
if (typeof file.is_text === 'boolean') return file.is_text
if (file.type) return file.type === 'text'
return Boolean(file.text)
}
const inferIsExpired = (file: FileListItem) => {
if (typeof file.isExpired === 'boolean') return file.isExpired
if (typeof file.is_expired === 'boolean') return file.is_expired
if (
file.expired_count !== null &&
file.expired_count !== undefined &&
file.expired_count <= 0
) {
return true
}
return isExpiredByDate(file.expired_at)
}
const inferIsChunked = (file: FileListItem) => Boolean(file.isChunked ?? file.is_chunked)
const getRemainingDownloads = (file: FileListItem) => {
if (file.remainingDownloads !== undefined) return file.remainingDownloads
if (file.remaining_downloads !== undefined) return file.remaining_downloads
if (
file.expired_count !== null &&
file.expired_count !== undefined &&
file.expired_count >= 0
) {
return Math.max(file.expired_count, 0)
}
return null
}
const buildFallbackSummary = (files: AdminFileViewItem[], total: number): AdminFileSummary => ({
totalFiles: total,
activeCount: files.filter((file) => !file.isExpiredFile).length,
expiredCount: files.filter((file) => file.isExpiredFile).length,
textCount: files.filter((file) => file.isTextFile).length,
fileCount: files.filter((file) => !file.isTextFile).length,
chunkedCount: files.filter((file) => file.isChunkedFile).length,
storageUsed: files.reduce((totalSize, file) => totalSize + normalizeCount(file.size), 0),
usedCount: files.reduce(
(totalUsed, file) => totalUsed + normalizeCount(file.usedCount ?? file.used_count),
0
)
})
const createFileViewItem = (file: FileListItem): AdminFileViewItem => {
const isTextFile = inferIsText(file)
const isExpiredFile = inferIsExpired(file)
const isChunkedFile = inferIsChunked(file)
const remainingDownloadsValue = getRemainingDownloads(file)
const usedCount = normalizeCount(file.usedCount ?? file.used_count)
return {
...file,
displayName: file.name || `${file.prefix}${file.suffix}` || file.code,
displaySize: formatFileSize(file.size),
displayExpiredAt: file.expired_at
? formatTimestamp(file.expired_at)
: t('send.expiration.units.forever'),
displayUsage: `${usedCount} ${t('common.times')}`,
isTextFile,
isExpiredFile,
isChunkedFile,
remainingDownloadsValue,
canPreviewText: Boolean(file.text)
}
}
const resetEditForm = () => {
editForm.value = {
id: null,
@@ -56,16 +169,23 @@ export function useAdminFiles() {
}
const loadFiles = async () => {
isLoading.value = true
try {
hasLoadError.value = false
const res = await FileService.getAdminFileList(params.value)
const res = await FileService.getAdminFileList(requestParams.value)
if (!res.detail) return
tableData.value = res.detail.data.map(createFileViewItem)
params.value.total = res.detail.total
summary.value = res.detail.summary || buildFallbackSummary(tableData.value, res.detail.total)
} catch (error) {
hasLoadError.value = true
alertStore.showAlert(getErrorMessage(error, t('manage.fileManage.loadFileListFailed')), 'error')
alertStore.showAlert(
getErrorMessage(error, t('manage.fileManage.loadFileListFailed')),
'error'
)
} finally {
isLoading.value = false
}
}
@@ -74,6 +194,32 @@ export function useAdminFiles() {
await loadFiles()
}
const refreshFiles = async () => {
await loadFiles()
}
const resetFilters = async () => {
params.value.page = 1
params.value.keyword = ''
params.value.status = 'all'
params.value.type = 'all'
params.value.sortBy = 'created_at'
params.value.sortOrder = 'desc'
await loadFiles()
}
const setStatusFilter = async (status: AdminFileStatusFilter) => {
params.value.status = status
params.value.page = 1
await loadFiles()
}
const setTypeFilter = async (type: AdminFileTypeFilter) => {
params.value.type = type
params.value.page = 1
await loadFiles()
}
const handlePageChange = async (page: number | string) => {
if (typeof page === 'string') return
if (page < 1 || page > totalPages.value) return
@@ -100,12 +246,17 @@ export function useAdminFiles() {
}
const handleUpdate = async () => {
if (isSaving.value) return
isSaving.value = true
try {
await FileService.updateFile(editForm.value)
await loadFiles()
closeEditModal()
} catch (error: unknown) {
alertStore.showAlert(getErrorMessage(error, t('manage.fileManage.updateFailed')), 'error')
} finally {
isSaving.value = false
}
}
@@ -116,6 +267,9 @@ export function useAdminFiles() {
try {
await FileService.deleteAdminFile(id)
if (tableData.value.length === 1 && params.value.page > 1) {
params.value.page -= 1
}
await loadFiles()
} catch (error: unknown) {
alertStore.showAlert(getErrorMessage(error, t('manage.fileManage.deleteFailed')), 'error')
@@ -143,7 +297,12 @@ export function useAdminFiles() {
return {
tableData,
hasLoadError,
hasActiveFilters,
isLoading,
isSaving,
params,
storageUsedText,
summary,
showEditModal,
editForm,
showTextPreview,
@@ -158,6 +317,10 @@ export function useAdminFiles() {
handleUpdate,
loadFiles,
openEditModal,
openTextPreview
openTextPreview,
refreshFiles,
resetFilters,
setStatusFilter,
setTypeFilter
}
}