feat: add admin file view presets
This commit is contained in:
@@ -22,6 +22,9 @@ import type {
|
|||||||
AdminFileStatusFilter,
|
AdminFileStatusFilter,
|
||||||
AdminFileSummary,
|
AdminFileSummary,
|
||||||
AdminFileTypeFilter,
|
AdminFileTypeFilter,
|
||||||
|
AdminFileViewPreset,
|
||||||
|
AdminFileViewPresetParams,
|
||||||
|
AdminFileViewPresetRequest,
|
||||||
AdminFileViewItem,
|
AdminFileViewItem,
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
FileEditForm,
|
FileEditForm,
|
||||||
@@ -117,6 +120,89 @@ const formatLocalDateTime = (date: Date) => {
|
|||||||
return `${year}-${month}-${day}T${hours}:${minutes}`
|
return `${year}-${month}-${day}T${hours}:${minutes}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const builtInAllViewPresetId = 'built_in_all'
|
||||||
|
const adminFileStatusFilters: AdminFileStatusFilter[] = ['all', 'active', 'expired']
|
||||||
|
const adminFileTypeFilters: AdminFileTypeFilter[] = ['all', 'file', 'text', 'chunked']
|
||||||
|
const adminFileHealthFilters: AdminFileHealthFilter[] = [
|
||||||
|
'all',
|
||||||
|
'attention',
|
||||||
|
'danger',
|
||||||
|
'warning',
|
||||||
|
'healthy',
|
||||||
|
'expired',
|
||||||
|
'expiring_soon',
|
||||||
|
'storage_issue',
|
||||||
|
'never_retrieved',
|
||||||
|
'permanent'
|
||||||
|
]
|
||||||
|
const adminFileSortFields = [
|
||||||
|
'created_at',
|
||||||
|
'expired_at',
|
||||||
|
'name',
|
||||||
|
'size',
|
||||||
|
'used_count',
|
||||||
|
'code'
|
||||||
|
] as const
|
||||||
|
const adminFileSortOrders = ['asc', 'desc'] as const
|
||||||
|
|
||||||
|
type RawViewPresetParams = Partial<
|
||||||
|
Record<keyof AdminFileViewPresetParams | 'sort_by' | 'sort_order', unknown>
|
||||||
|
>
|
||||||
|
|
||||||
|
const normalizePresetChoice = <T extends string>(
|
||||||
|
value: unknown,
|
||||||
|
allowedValues: readonly T[],
|
||||||
|
fallback: T
|
||||||
|
): T => {
|
||||||
|
const normalizedValue = String(value ?? fallback)
|
||||||
|
.replace('-', '_')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
return allowedValues.includes(normalizedValue as T) ? (normalizedValue as T) : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizePresetSize = (value: unknown) => {
|
||||||
|
const normalizedSize = Number(value)
|
||||||
|
if (!Number.isFinite(normalizedSize)) return 10
|
||||||
|
return Math.min(Math.max(Math.trunc(normalizedSize), 1), 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildDefaultViewPresetParams = (): AdminFileViewPresetParams => ({
|
||||||
|
keyword: '',
|
||||||
|
status: 'all',
|
||||||
|
type: 'all',
|
||||||
|
health: 'all',
|
||||||
|
sortBy: 'created_at',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
size: 10
|
||||||
|
})
|
||||||
|
|
||||||
|
const normalizeViewPresetParams = (params: unknown): AdminFileViewPresetParams => {
|
||||||
|
const defaults = buildDefaultViewPresetParams()
|
||||||
|
const rawParams =
|
||||||
|
params && typeof params === 'object'
|
||||||
|
? (params as RawViewPresetParams)
|
||||||
|
: ({} as RawViewPresetParams)
|
||||||
|
|
||||||
|
return {
|
||||||
|
keyword: String(rawParams.keyword ?? defaults.keyword).trim(),
|
||||||
|
status: normalizePresetChoice(rawParams.status, adminFileStatusFilters, defaults.status),
|
||||||
|
type: normalizePresetChoice(rawParams.type, adminFileTypeFilters, defaults.type),
|
||||||
|
health: normalizePresetChoice(rawParams.health, adminFileHealthFilters, defaults.health),
|
||||||
|
sortBy: normalizePresetChoice(
|
||||||
|
rawParams.sortBy ?? rawParams.sort_by,
|
||||||
|
adminFileSortFields,
|
||||||
|
defaults.sortBy
|
||||||
|
),
|
||||||
|
sortOrder: normalizePresetChoice(
|
||||||
|
rawParams.sortOrder ?? rawParams.sort_order,
|
||||||
|
adminFileSortOrders,
|
||||||
|
defaults.sortOrder
|
||||||
|
),
|
||||||
|
size: normalizePresetSize(rawParams.size ?? defaults.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useAdminFiles() {
|
export function useAdminFiles() {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const alertStore = useAlertStore()
|
const alertStore = useAlertStore()
|
||||||
@@ -166,6 +252,11 @@ export function useAdminFiles() {
|
|||||||
const isBatchDeleting = ref(false)
|
const isBatchDeleting = ref(false)
|
||||||
const isBatchUpdating = ref(false)
|
const isBatchUpdating = ref(false)
|
||||||
const isBatchPolicyActionRunning = ref(false)
|
const isBatchPolicyActionRunning = ref(false)
|
||||||
|
const savedViewPresets = ref<AdminFileViewPreset[]>([])
|
||||||
|
const selectedViewPresetId = ref(builtInAllViewPresetId)
|
||||||
|
const isViewPresetLoading = ref(false)
|
||||||
|
const isViewPresetSaving = ref(false)
|
||||||
|
const isViewPresetDeleting = ref(false)
|
||||||
let detailRequestSerial = 0
|
let detailRequestSerial = 0
|
||||||
const isBatchActionRunning = computed(
|
const isBatchActionRunning = computed(
|
||||||
() => isBatchDeleting.value || isBatchUpdating.value || isBatchPolicyActionRunning.value
|
() => isBatchDeleting.value || isBatchUpdating.value || isBatchPolicyActionRunning.value
|
||||||
@@ -203,6 +294,61 @@ export function useAdminFiles() {
|
|||||||
params.value.health !== 'all'
|
params.value.health !== 'all'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const buildCurrentViewPresetParams = (): AdminFileViewPresetParams => ({
|
||||||
|
keyword: params.value.keyword || '',
|
||||||
|
status: params.value.status || 'all',
|
||||||
|
type: params.value.type || 'all',
|
||||||
|
health: params.value.health || 'all',
|
||||||
|
sortBy: params.value.sortBy || 'created_at',
|
||||||
|
sortOrder: params.value.sortOrder || 'desc',
|
||||||
|
size: params.value.size || 10
|
||||||
|
})
|
||||||
|
|
||||||
|
const buildBuiltInViewPreset = (
|
||||||
|
id: string,
|
||||||
|
nameKey: string,
|
||||||
|
filters: Partial<AdminFileViewPresetParams> = {}
|
||||||
|
): AdminFileViewPreset => {
|
||||||
|
const presetFilters = normalizeViewPresetParams({
|
||||||
|
...buildDefaultViewPresetParams(),
|
||||||
|
...filters
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: t(nameKey),
|
||||||
|
filters: presetFilters,
|
||||||
|
params: presetFilters,
|
||||||
|
isBuiltIn: true,
|
||||||
|
isDefault: id === builtInAllViewPresetId,
|
||||||
|
is_default: id === builtInAllViewPresetId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const builtInViewPresets = computed<AdminFileViewPreset[]>(() => [
|
||||||
|
buildBuiltInViewPreset(builtInAllViewPresetId, 'fileManage.viewPresetAll'),
|
||||||
|
buildBuiltInViewPreset('built_in_attention', 'fileManage.viewPresetAttention', {
|
||||||
|
health: 'attention'
|
||||||
|
}),
|
||||||
|
buildBuiltInViewPreset('built_in_expiring_soon', 'fileManage.viewPresetExpiringSoon', {
|
||||||
|
health: 'expiring_soon'
|
||||||
|
}),
|
||||||
|
buildBuiltInViewPreset('built_in_storage_issue', 'fileManage.viewPresetStorageIssue', {
|
||||||
|
health: 'storage_issue'
|
||||||
|
}),
|
||||||
|
buildBuiltInViewPreset('built_in_never_retrieved', 'fileManage.viewPresetNeverRetrieved', {
|
||||||
|
health: 'never_retrieved'
|
||||||
|
}),
|
||||||
|
buildBuiltInViewPreset('built_in_permanent', 'fileManage.viewPresetPermanent', {
|
||||||
|
health: 'permanent'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
|
||||||
|
const viewPresets = computed<AdminFileViewPreset[]>(() => [
|
||||||
|
...builtInViewPresets.value,
|
||||||
|
...savedViewPresets.value
|
||||||
|
])
|
||||||
|
|
||||||
const healthFilterOptions = computed<HealthFilterOption[]>(() => [
|
const healthFilterOptions = computed<HealthFilterOption[]>(() => [
|
||||||
{ value: 'all', label: t('fileManage.healthFilters.all'), count: summary.value.totalFiles },
|
{ value: 'all', label: t('fileManage.healthFilters.all'), count: summary.value.totalFiles },
|
||||||
{
|
{
|
||||||
@@ -766,6 +912,161 @@ export function useAdminFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPresetParams = (preset: AdminFileViewPreset) =>
|
||||||
|
normalizeViewPresetParams(preset.filters ?? preset.params)
|
||||||
|
|
||||||
|
const normalizeSavedViewPreset = (preset: AdminFileViewPreset): AdminFileViewPreset | null => {
|
||||||
|
const id = String(preset.id || '').trim()
|
||||||
|
const name = String(preset.name || '').trim()
|
||||||
|
if (!id || !name) return null
|
||||||
|
|
||||||
|
const presetParams = getPresetParams(preset)
|
||||||
|
return {
|
||||||
|
...preset,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
filters: presetParams,
|
||||||
|
params: presetParams,
|
||||||
|
isBuiltIn: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const upsertSavedViewPreset = (preset: AdminFileViewPreset) => {
|
||||||
|
const normalizedPreset = normalizeSavedViewPreset(preset)
|
||||||
|
if (!normalizedPreset) return null
|
||||||
|
|
||||||
|
savedViewPresets.value = [
|
||||||
|
...savedViewPresets.value.filter((item) => item.id !== normalizedPreset.id),
|
||||||
|
normalizedPreset
|
||||||
|
]
|
||||||
|
return normalizedPreset
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildViewPresetRequest = (name: string, id?: string): AdminFileViewPresetRequest | null => {
|
||||||
|
const normalizedName = name.trim()
|
||||||
|
if (!normalizedName) return null
|
||||||
|
|
||||||
|
const filters = normalizeViewPresetParams(buildCurrentViewPresetParams())
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: normalizedName,
|
||||||
|
filters,
|
||||||
|
params: filters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const markViewPresetDirty = () => {
|
||||||
|
selectedViewPresetId.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadViewPresets = async () => {
|
||||||
|
isViewPresetLoading.value = true
|
||||||
|
try {
|
||||||
|
const response = await FileService.getAdminFileViewPresets()
|
||||||
|
const rawPresets = response.detail?.presets ?? response.detail?.items ?? []
|
||||||
|
savedViewPresets.value = rawPresets
|
||||||
|
.map((preset) => normalizeSavedViewPreset(preset))
|
||||||
|
.filter((preset): preset is AdminFileViewPreset => Boolean(preset))
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedViewPresetId.value &&
|
||||||
|
!viewPresets.value.some((preset) => preset.id === selectedViewPresetId.value)
|
||||||
|
) {
|
||||||
|
selectedViewPresetId.value = builtInAllViewPresetId
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
savedViewPresets.value = []
|
||||||
|
alertStore.showAlert(
|
||||||
|
isLegacyEndpointUnavailable(error)
|
||||||
|
? t('fileManage.viewPresetLoadFailed')
|
||||||
|
: getErrorMessage(error, t('fileManage.viewPresetLoadFailed')),
|
||||||
|
'warning'
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
isViewPresetLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyViewPreset = async (idOrPreset: string | AdminFileViewPreset) => {
|
||||||
|
const preset =
|
||||||
|
typeof idOrPreset === 'string'
|
||||||
|
? viewPresets.value.find((item) => item.id === idOrPreset)
|
||||||
|
: idOrPreset
|
||||||
|
if (!preset) return
|
||||||
|
|
||||||
|
const presetParams = getPresetParams(preset)
|
||||||
|
params.value.keyword = presetParams.keyword
|
||||||
|
params.value.status = presetParams.status
|
||||||
|
params.value.type = presetParams.type
|
||||||
|
params.value.health = presetParams.health
|
||||||
|
params.value.sortBy = presetParams.sortBy
|
||||||
|
params.value.sortOrder = presetParams.sortOrder
|
||||||
|
params.value.size = presetParams.size
|
||||||
|
params.value.page = 1
|
||||||
|
selectedViewPresetId.value = preset.id
|
||||||
|
clearSelection()
|
||||||
|
await loadFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveCurrentViewPreset = async (name: string) => {
|
||||||
|
const request = buildViewPresetRequest(name)
|
||||||
|
if (!request || isViewPresetSaving.value) return null
|
||||||
|
|
||||||
|
isViewPresetSaving.value = true
|
||||||
|
try {
|
||||||
|
const response = await FileService.saveAdminFileViewPreset(request)
|
||||||
|
const savedPreset = response.detail ? upsertSavedViewPreset(response.detail) : null
|
||||||
|
selectedViewPresetId.value = savedPreset?.id || selectedViewPresetId.value
|
||||||
|
alertStore.showAlert(t('fileManage.viewPresetSaveSuccess'), 'success')
|
||||||
|
return savedPreset
|
||||||
|
} catch (error: unknown) {
|
||||||
|
alertStore.showAlert(getErrorMessage(error, t('fileManage.viewPresetSaveFailed')), 'error')
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
isViewPresetSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSelectedViewPreset = async () => {
|
||||||
|
const preset = savedViewPresets.value.find((item) => item.id === selectedViewPresetId.value)
|
||||||
|
if (!preset || isViewPresetSaving.value) return null
|
||||||
|
|
||||||
|
const request = buildViewPresetRequest(preset.name, preset.id)
|
||||||
|
if (!request) return null
|
||||||
|
|
||||||
|
isViewPresetSaving.value = true
|
||||||
|
try {
|
||||||
|
const response = await FileService.saveAdminFileViewPreset(request)
|
||||||
|
const savedPreset = response.detail ? upsertSavedViewPreset(response.detail) : null
|
||||||
|
selectedViewPresetId.value = savedPreset?.id || preset.id
|
||||||
|
alertStore.showAlert(t('fileManage.viewPresetSaveSuccess'), 'success')
|
||||||
|
return savedPreset
|
||||||
|
} catch (error: unknown) {
|
||||||
|
alertStore.showAlert(getErrorMessage(error, t('fileManage.viewPresetSaveFailed')), 'error')
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
isViewPresetSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSelectedViewPreset = async () => {
|
||||||
|
const preset = savedViewPresets.value.find((item) => item.id === selectedViewPresetId.value)
|
||||||
|
if (!preset || isViewPresetDeleting.value) return
|
||||||
|
if (!window.confirm(t('fileManage.viewPresetDeleteConfirm', { name: preset.name }))) return
|
||||||
|
|
||||||
|
isViewPresetDeleting.value = true
|
||||||
|
try {
|
||||||
|
await FileService.deleteAdminFileViewPreset(preset.id)
|
||||||
|
savedViewPresets.value = savedViewPresets.value.filter((item) => item.id !== preset.id)
|
||||||
|
markViewPresetDirty()
|
||||||
|
alertStore.showAlert(t('fileManage.viewPresetDeleteSuccess'), 'success')
|
||||||
|
} catch (error: unknown) {
|
||||||
|
alertStore.showAlert(getErrorMessage(error, t('fileManage.viewPresetDeleteFailed')), 'error')
|
||||||
|
} finally {
|
||||||
|
isViewPresetDeleting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const refreshSelectedFileDetail = async (fileId: number, detail?: AdminFileDetailResponse) => {
|
const refreshSelectedFileDetail = async (fileId: number, detail?: AdminFileDetailResponse) => {
|
||||||
if (detail) {
|
if (detail) {
|
||||||
setSelectedFileDetail(detail)
|
setSelectedFileDetail(detail)
|
||||||
@@ -812,6 +1113,7 @@ export function useAdminFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSearch = async () => {
|
const handleSearch = async () => {
|
||||||
|
markViewPresetDirty()
|
||||||
params.value.page = 1
|
params.value.page = 1
|
||||||
await loadFiles()
|
await loadFiles()
|
||||||
}
|
}
|
||||||
@@ -828,23 +1130,27 @@ export function useAdminFiles() {
|
|||||||
params.value.health = 'all'
|
params.value.health = 'all'
|
||||||
params.value.sortBy = 'created_at'
|
params.value.sortBy = 'created_at'
|
||||||
params.value.sortOrder = 'desc'
|
params.value.sortOrder = 'desc'
|
||||||
|
selectedViewPresetId.value = builtInAllViewPresetId
|
||||||
clearSelection()
|
clearSelection()
|
||||||
await loadFiles()
|
await loadFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
const setStatusFilter = async (status: AdminFileStatusFilter) => {
|
const setStatusFilter = async (status: AdminFileStatusFilter) => {
|
||||||
|
markViewPresetDirty()
|
||||||
params.value.status = status
|
params.value.status = status
|
||||||
params.value.page = 1
|
params.value.page = 1
|
||||||
await loadFiles()
|
await loadFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
const setTypeFilter = async (type: AdminFileTypeFilter) => {
|
const setTypeFilter = async (type: AdminFileTypeFilter) => {
|
||||||
|
markViewPresetDirty()
|
||||||
params.value.type = type
|
params.value.type = type
|
||||||
params.value.page = 1
|
params.value.page = 1
|
||||||
await loadFiles()
|
await loadFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
const setHealthFilter = async (health: AdminFileHealthFilter) => {
|
const setHealthFilter = async (health: AdminFileHealthFilter) => {
|
||||||
|
markViewPresetDirty()
|
||||||
params.value.health = health
|
params.value.health = health
|
||||||
params.value.page = 1
|
params.value.page = 1
|
||||||
await loadFiles()
|
await loadFiles()
|
||||||
@@ -1342,6 +1648,9 @@ export function useAdminFiles() {
|
|||||||
isDetailPolicyActionRunning,
|
isDetailPolicyActionRunning,
|
||||||
isPreviewLoading,
|
isPreviewLoading,
|
||||||
isSaving,
|
isSaving,
|
||||||
|
isViewPresetDeleting,
|
||||||
|
isViewPresetLoading,
|
||||||
|
isViewPresetSaving,
|
||||||
batchEditForm,
|
batchEditForm,
|
||||||
batchPolicyActionOptions,
|
batchPolicyActionOptions,
|
||||||
detailPolicyActionOptions,
|
detailPolicyActionOptions,
|
||||||
@@ -1355,6 +1664,7 @@ export function useAdminFiles() {
|
|||||||
selectedFileDetail,
|
selectedFileDetail,
|
||||||
selectedCount,
|
selectedCount,
|
||||||
selectedFileIds,
|
selectedFileIds,
|
||||||
|
selectedViewPresetId,
|
||||||
storageUsedText,
|
storageUsedText,
|
||||||
summary,
|
summary,
|
||||||
showBatchEditModal,
|
showBatchEditModal,
|
||||||
@@ -1364,6 +1674,8 @@ export function useAdminFiles() {
|
|||||||
showTextPreview,
|
showTextPreview,
|
||||||
previewText,
|
previewText,
|
||||||
totalPages,
|
totalPages,
|
||||||
|
viewPresets,
|
||||||
|
applyViewPreset,
|
||||||
closeEditModal,
|
closeEditModal,
|
||||||
closeBatchEditModal,
|
closeBatchEditModal,
|
||||||
closeFileDetail,
|
closeFileDetail,
|
||||||
@@ -1374,6 +1686,7 @@ export function useAdminFiles() {
|
|||||||
clearSelection,
|
clearSelection,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
deleteSelectedFiles,
|
deleteSelectedFiles,
|
||||||
|
deleteSelectedViewPreset,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
exportPreviewText,
|
exportPreviewText,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
@@ -1382,6 +1695,7 @@ export function useAdminFiles() {
|
|||||||
applySelectedPolicyAction,
|
applySelectedPolicyAction,
|
||||||
handleBatchUpdate,
|
handleBatchUpdate,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
|
loadViewPresets,
|
||||||
loadFiles,
|
loadFiles,
|
||||||
openBatchEditModal,
|
openBatchEditModal,
|
||||||
openEditModal,
|
openEditModal,
|
||||||
@@ -1389,9 +1703,11 @@ export function useAdminFiles() {
|
|||||||
openTextPreview,
|
openTextPreview,
|
||||||
refreshFiles,
|
refreshFiles,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
|
saveCurrentViewPreset,
|
||||||
setHealthFilter,
|
setHealthFilter,
|
||||||
setStatusFilter,
|
setStatusFilter,
|
||||||
setTypeFilter,
|
setTypeFilter,
|
||||||
|
updateSelectedViewPreset,
|
||||||
updateDetailMetadata,
|
updateDetailMetadata,
|
||||||
toggleCurrentPageSelection,
|
toggleCurrentPageSelection,
|
||||||
toggleFileSelection
|
toggleFileSelection
|
||||||
|
|||||||
@@ -418,6 +418,24 @@ export default {
|
|||||||
statusLabel: 'Status',
|
statusLabel: 'Status',
|
||||||
typeLabel: 'Type',
|
typeLabel: 'Type',
|
||||||
healthLabel: 'Health',
|
healthLabel: 'Health',
|
||||||
|
viewPreset: 'View',
|
||||||
|
viewPresetCustom: 'Custom Filters',
|
||||||
|
viewPresetAll: 'All Files',
|
||||||
|
viewPresetAttention: 'Needs Attention',
|
||||||
|
viewPresetExpiringSoon: 'Expiring Soon',
|
||||||
|
viewPresetStorageIssue: 'Storage Issues',
|
||||||
|
viewPresetNeverRetrieved: 'Never Retrieved',
|
||||||
|
viewPresetPermanent: 'Permanent',
|
||||||
|
saveViewPreset: 'Save View',
|
||||||
|
updateViewPreset: 'Update View',
|
||||||
|
deleteViewPreset: 'Delete View',
|
||||||
|
viewPresetNamePrompt: 'Enter a view name',
|
||||||
|
viewPresetSaveSuccess: 'View saved',
|
||||||
|
viewPresetSaveFailed: 'Failed to save view',
|
||||||
|
viewPresetDeleteConfirm: 'Delete view "{name}"?',
|
||||||
|
viewPresetDeleteSuccess: 'View deleted',
|
||||||
|
viewPresetDeleteFailed: 'Failed to delete view',
|
||||||
|
viewPresetLoadFailed: 'Failed to load saved views; using built-in views',
|
||||||
all: 'All',
|
all: 'All',
|
||||||
active: 'Available',
|
active: 'Available',
|
||||||
expired: 'Expired',
|
expired: 'Expired',
|
||||||
|
|||||||
@@ -416,6 +416,24 @@ export default {
|
|||||||
statusLabel: '状态',
|
statusLabel: '状态',
|
||||||
typeLabel: '类型',
|
typeLabel: '类型',
|
||||||
healthLabel: '健康',
|
healthLabel: '健康',
|
||||||
|
viewPreset: '视图',
|
||||||
|
viewPresetCustom: '自定义筛选',
|
||||||
|
viewPresetAll: '全部文件',
|
||||||
|
viewPresetAttention: '需关注',
|
||||||
|
viewPresetExpiringSoon: '即将过期',
|
||||||
|
viewPresetStorageIssue: '存储异常',
|
||||||
|
viewPresetNeverRetrieved: '未取件',
|
||||||
|
viewPresetPermanent: '永久有效',
|
||||||
|
saveViewPreset: '保存视图',
|
||||||
|
updateViewPreset: '覆盖视图',
|
||||||
|
deleteViewPreset: '删除视图',
|
||||||
|
viewPresetNamePrompt: '输入视图名称',
|
||||||
|
viewPresetSaveSuccess: '视图已保存',
|
||||||
|
viewPresetSaveFailed: '视图保存失败',
|
||||||
|
viewPresetDeleteConfirm: '确认删除视图“{name}”?',
|
||||||
|
viewPresetDeleteSuccess: '视图已删除',
|
||||||
|
viewPresetDeleteFailed: '视图删除失败',
|
||||||
|
viewPresetLoadFailed: '视图预设加载失败,已使用内置视图',
|
||||||
all: '全部',
|
all: '全部',
|
||||||
active: '可取件',
|
active: '可取件',
|
||||||
expired: '已过期',
|
expired: '已过期',
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import type {
|
|||||||
AdminFileMetadataRequest,
|
AdminFileMetadataRequest,
|
||||||
AdminFilePolicyActionRequest,
|
AdminFilePolicyActionRequest,
|
||||||
AdminFilePreviewResponse,
|
AdminFilePreviewResponse,
|
||||||
|
AdminFileViewPreset,
|
||||||
|
AdminFileViewPresetRequest,
|
||||||
|
AdminFileViewPresetsResponse,
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
ChunkUploadCompleteRequest,
|
ChunkUploadCompleteRequest,
|
||||||
ChunkUploadInitRequest,
|
ChunkUploadInitRequest,
|
||||||
@@ -190,6 +193,24 @@ export class FileService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getAdminFileViewPresets(): Promise<ApiResponse<AdminFileViewPresetsResponse>> {
|
||||||
|
return api.get('/admin/file/view-presets')
|
||||||
|
}
|
||||||
|
|
||||||
|
static async saveAdminFileViewPreset(
|
||||||
|
data: AdminFileViewPresetRequest
|
||||||
|
): Promise<ApiResponse<AdminFileViewPreset>> {
|
||||||
|
try {
|
||||||
|
return await api.patch('/admin/file/view-presets', data)
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (!isMethodFallbackError(error)) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.post('/admin/file/view-presets', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async updateAdminFiles(
|
static async updateAdminFiles(
|
||||||
data: AdminBatchUpdateFilesRequest
|
data: AdminBatchUpdateFilesRequest
|
||||||
): Promise<ApiResponse<AdminBatchUpdateFilesResponse>> {
|
): Promise<ApiResponse<AdminBatchUpdateFilesResponse>> {
|
||||||
@@ -210,6 +231,20 @@ export class FileService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async deleteAdminFileViewPreset(id: string): Promise<ApiResponse> {
|
||||||
|
try {
|
||||||
|
return await api.delete('/admin/file/view-presets', {
|
||||||
|
data: { id }
|
||||||
|
})
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (!isMethodFallbackError(error)) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.post('/admin/file/view-presets/delete', { id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async downloadAdminFile(
|
static async downloadAdminFile(
|
||||||
id: number
|
id: number
|
||||||
): Promise<{ data: Blob; headers: Record<string, string> }> {
|
): Promise<{ data: Blob; headers: Record<string, string> }> {
|
||||||
|
|||||||
@@ -102,6 +102,43 @@ export interface AdminFileListParams {
|
|||||||
sortOrder?: AdminFileSortOrder
|
sortOrder?: AdminFileSortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AdminFileViewPresetParams {
|
||||||
|
keyword: string
|
||||||
|
status: AdminFileStatusFilter
|
||||||
|
type: AdminFileTypeFilter
|
||||||
|
health: AdminFileHealthFilter
|
||||||
|
sortBy: AdminFileSortBy
|
||||||
|
sortOrder: AdminFileSortOrder
|
||||||
|
size: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminFileViewPreset {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
filters?: AdminFileViewPresetParams
|
||||||
|
params?: AdminFileViewPresetParams
|
||||||
|
isBuiltIn?: boolean
|
||||||
|
isDefault?: boolean
|
||||||
|
is_default?: boolean
|
||||||
|
createdAt?: string | null
|
||||||
|
created_at?: string | null
|
||||||
|
updatedAt?: string | null
|
||||||
|
updated_at?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminFileViewPresetRequest {
|
||||||
|
id?: string
|
||||||
|
name: string
|
||||||
|
filters: AdminFileViewPresetParams
|
||||||
|
params?: AdminFileViewPresetParams
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminFileViewPresetsResponse {
|
||||||
|
presets?: AdminFileViewPreset[]
|
||||||
|
items?: AdminFileViewPreset[]
|
||||||
|
total?: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface FileEditForm {
|
export interface FileEditForm {
|
||||||
id: number | null
|
id: number | null
|
||||||
code: string
|
code: string
|
||||||
|
|||||||
@@ -159,6 +159,64 @@
|
|||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
<div class="flex flex-col gap-2 sm:ml-auto sm:flex-row sm:items-center">
|
||||||
|
<label class="inline-flex items-center text-sm" :class="[mutedTextClass]">
|
||||||
|
<FilterIcon class="mr-1 h-3.5 w-3.5" />
|
||||||
|
{{ t('fileManage.viewPreset') }}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
:value="selectedViewPresetId"
|
||||||
|
class="min-w-[160px] rounded-lg border px-3 py-2 text-sm focus:border-transparent focus:ring-2 focus:ring-indigo-500"
|
||||||
|
:class="[fieldClass]"
|
||||||
|
:disabled="isViewPresetLoading"
|
||||||
|
@change="handleViewPresetChange"
|
||||||
|
>
|
||||||
|
<option value="">{{ t('fileManage.viewPresetCustom') }}</option>
|
||||||
|
<option v-for="preset in viewPresets" :key="preset.id" :value="preset.id">
|
||||||
|
{{ preset.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<BaseButton
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
:title="t('fileManage.saveViewPreset')"
|
||||||
|
:loading="isViewPresetSaving"
|
||||||
|
@click="handleSaveViewPreset"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<CheckIcon class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{ t('fileManage.saveViewPreset') }}
|
||||||
|
</BaseButton>
|
||||||
|
<BaseButton
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
:title="t('fileManage.updateViewPreset')"
|
||||||
|
:disabled="!canModifySelectedViewPreset"
|
||||||
|
:loading="isViewPresetSaving"
|
||||||
|
@click="updateSelectedViewPreset"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<PencilIcon class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{ t('fileManage.updateViewPreset') }}
|
||||||
|
</BaseButton>
|
||||||
|
<BaseButton
|
||||||
|
size="sm"
|
||||||
|
variant="danger"
|
||||||
|
:title="t('fileManage.deleteViewPreset')"
|
||||||
|
:disabled="!canModifySelectedViewPreset"
|
||||||
|
:loading="isViewPresetDeleting"
|
||||||
|
@click="deleteSelectedViewPreset"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<TrashIcon class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{ t('fileManage.deleteViewPreset') }}
|
||||||
|
</BaseButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -1178,6 +1236,9 @@ const {
|
|||||||
isDetailPolicyActionRunning,
|
isDetailPolicyActionRunning,
|
||||||
isPreviewLoading,
|
isPreviewLoading,
|
||||||
isSaving,
|
isSaving,
|
||||||
|
isViewPresetDeleting,
|
||||||
|
isViewPresetLoading,
|
||||||
|
isViewPresetSaving,
|
||||||
batchEditForm,
|
batchEditForm,
|
||||||
batchPolicyActionOptions,
|
batchPolicyActionOptions,
|
||||||
detailPolicyActionOptions,
|
detailPolicyActionOptions,
|
||||||
@@ -1191,6 +1252,7 @@ const {
|
|||||||
selectedFileDetail,
|
selectedFileDetail,
|
||||||
selectedCount,
|
selectedCount,
|
||||||
selectedFileIds,
|
selectedFileIds,
|
||||||
|
selectedViewPresetId,
|
||||||
storageUsedText,
|
storageUsedText,
|
||||||
summary,
|
summary,
|
||||||
showBatchEditModal,
|
showBatchEditModal,
|
||||||
@@ -1199,6 +1261,8 @@ const {
|
|||||||
editForm,
|
editForm,
|
||||||
showTextPreview,
|
showTextPreview,
|
||||||
previewText,
|
previewText,
|
||||||
|
viewPresets,
|
||||||
|
applyViewPreset,
|
||||||
closeBatchEditModal,
|
closeBatchEditModal,
|
||||||
closeEditModal,
|
closeEditModal,
|
||||||
closeFileDetail,
|
closeFileDetail,
|
||||||
@@ -1209,6 +1273,7 @@ const {
|
|||||||
clearSelection,
|
clearSelection,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
deleteSelectedFiles,
|
deleteSelectedFiles,
|
||||||
|
deleteSelectedViewPreset,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
exportPreviewText,
|
exportPreviewText,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
@@ -1217,6 +1282,7 @@ const {
|
|||||||
applySelectedPolicyAction,
|
applySelectedPolicyAction,
|
||||||
handleBatchUpdate,
|
handleBatchUpdate,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
|
loadViewPresets,
|
||||||
loadFiles,
|
loadFiles,
|
||||||
openBatchEditModal,
|
openBatchEditModal,
|
||||||
openEditModal,
|
openEditModal,
|
||||||
@@ -1224,9 +1290,11 @@ const {
|
|||||||
openTextPreview,
|
openTextPreview,
|
||||||
refreshFiles,
|
refreshFiles,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
|
saveCurrentViewPreset,
|
||||||
setHealthFilter,
|
setHealthFilter,
|
||||||
setStatusFilter,
|
setStatusFilter,
|
||||||
setTypeFilter,
|
setTypeFilter,
|
||||||
|
updateSelectedViewPreset,
|
||||||
updateDetailMetadata,
|
updateDetailMetadata,
|
||||||
toggleCurrentPageSelection,
|
toggleCurrentPageSelection,
|
||||||
toggleFileSelection
|
toggleFileSelection
|
||||||
@@ -1364,6 +1432,11 @@ const detailInsightReasonLabels = computed(() => {
|
|||||||
return file.statusInsightReasons.map((reason) => t(`fileManage.insightReasons.${reason}`))
|
return file.statusInsightReasons.map((reason) => t(`fileManage.insightReasons.${reason}`))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const canModifySelectedViewPreset = computed(() => {
|
||||||
|
const preset = viewPresets.value.find((item) => item.id === selectedViewPresetId.value)
|
||||||
|
return Boolean(preset && !preset.isBuiltIn)
|
||||||
|
})
|
||||||
|
|
||||||
const openDetailEditModal = () => {
|
const openDetailEditModal = () => {
|
||||||
if (!selectedFileDetail.value) return
|
if (!selectedFileDetail.value) return
|
||||||
|
|
||||||
@@ -1479,6 +1552,24 @@ const handleResetFilters = async () => {
|
|||||||
await syncHealthFilterQuery('all')
|
await syncHealthFilterQuery('all')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleViewPresetChange = async (event: Event) => {
|
||||||
|
const target = event.target as HTMLSelectElement
|
||||||
|
if (!target.value) {
|
||||||
|
selectedViewPresetId.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await applyViewPreset(target.value)
|
||||||
|
await syncHealthFilterQuery(params.value.health || 'all')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveViewPreset = async () => {
|
||||||
|
const name = window.prompt(t('fileManage.viewPresetNamePrompt'))
|
||||||
|
if (!name) return
|
||||||
|
|
||||||
|
await saveCurrentViewPreset(name)
|
||||||
|
}
|
||||||
|
|
||||||
const batchEditModeOptions = computed<
|
const batchEditModeOptions = computed<
|
||||||
{ value: AdminBatchEditMode; label: string; icon: Component }[]
|
{ value: AdminBatchEditMode; label: string; icon: Component }[]
|
||||||
>(() => [
|
>(() => [
|
||||||
@@ -1626,6 +1717,7 @@ watch(
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
params.value.health = getRouteHealthFilter()
|
params.value.health = getRouteHealthFilter()
|
||||||
|
void loadViewPresets()
|
||||||
void loadFiles()
|
void loadFiles()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user