feat: add admin file view presets
This commit is contained in:
@@ -22,6 +22,9 @@ import type {
|
||||
AdminFileStatusFilter,
|
||||
AdminFileSummary,
|
||||
AdminFileTypeFilter,
|
||||
AdminFileViewPreset,
|
||||
AdminFileViewPresetParams,
|
||||
AdminFileViewPresetRequest,
|
||||
AdminFileViewItem,
|
||||
ApiResponse,
|
||||
FileEditForm,
|
||||
@@ -117,6 +120,89 @@ const formatLocalDateTime = (date: Date) => {
|
||||
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() {
|
||||
const { t } = useI18n()
|
||||
const alertStore = useAlertStore()
|
||||
@@ -166,6 +252,11 @@ export function useAdminFiles() {
|
||||
const isBatchDeleting = ref(false)
|
||||
const isBatchUpdating = 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
|
||||
const isBatchActionRunning = computed(
|
||||
() => isBatchDeleting.value || isBatchUpdating.value || isBatchPolicyActionRunning.value
|
||||
@@ -203,6 +294,61 @@ export function useAdminFiles() {
|
||||
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[]>(() => [
|
||||
{ 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) => {
|
||||
if (detail) {
|
||||
setSelectedFileDetail(detail)
|
||||
@@ -812,6 +1113,7 @@ export function useAdminFiles() {
|
||||
}
|
||||
|
||||
const handleSearch = async () => {
|
||||
markViewPresetDirty()
|
||||
params.value.page = 1
|
||||
await loadFiles()
|
||||
}
|
||||
@@ -828,23 +1130,27 @@ export function useAdminFiles() {
|
||||
params.value.health = 'all'
|
||||
params.value.sortBy = 'created_at'
|
||||
params.value.sortOrder = 'desc'
|
||||
selectedViewPresetId.value = builtInAllViewPresetId
|
||||
clearSelection()
|
||||
await loadFiles()
|
||||
}
|
||||
|
||||
const setStatusFilter = async (status: AdminFileStatusFilter) => {
|
||||
markViewPresetDirty()
|
||||
params.value.status = status
|
||||
params.value.page = 1
|
||||
await loadFiles()
|
||||
}
|
||||
|
||||
const setTypeFilter = async (type: AdminFileTypeFilter) => {
|
||||
markViewPresetDirty()
|
||||
params.value.type = type
|
||||
params.value.page = 1
|
||||
await loadFiles()
|
||||
}
|
||||
|
||||
const setHealthFilter = async (health: AdminFileHealthFilter) => {
|
||||
markViewPresetDirty()
|
||||
params.value.health = health
|
||||
params.value.page = 1
|
||||
await loadFiles()
|
||||
@@ -1342,6 +1648,9 @@ export function useAdminFiles() {
|
||||
isDetailPolicyActionRunning,
|
||||
isPreviewLoading,
|
||||
isSaving,
|
||||
isViewPresetDeleting,
|
||||
isViewPresetLoading,
|
||||
isViewPresetSaving,
|
||||
batchEditForm,
|
||||
batchPolicyActionOptions,
|
||||
detailPolicyActionOptions,
|
||||
@@ -1355,6 +1664,7 @@ export function useAdminFiles() {
|
||||
selectedFileDetail,
|
||||
selectedCount,
|
||||
selectedFileIds,
|
||||
selectedViewPresetId,
|
||||
storageUsedText,
|
||||
summary,
|
||||
showBatchEditModal,
|
||||
@@ -1364,6 +1674,8 @@ export function useAdminFiles() {
|
||||
showTextPreview,
|
||||
previewText,
|
||||
totalPages,
|
||||
viewPresets,
|
||||
applyViewPreset,
|
||||
closeEditModal,
|
||||
closeBatchEditModal,
|
||||
closeFileDetail,
|
||||
@@ -1374,6 +1686,7 @@ export function useAdminFiles() {
|
||||
clearSelection,
|
||||
deleteFile,
|
||||
deleteSelectedFiles,
|
||||
deleteSelectedViewPreset,
|
||||
downloadFile,
|
||||
exportPreviewText,
|
||||
handlePageChange,
|
||||
@@ -1382,6 +1695,7 @@ export function useAdminFiles() {
|
||||
applySelectedPolicyAction,
|
||||
handleBatchUpdate,
|
||||
handleUpdate,
|
||||
loadViewPresets,
|
||||
loadFiles,
|
||||
openBatchEditModal,
|
||||
openEditModal,
|
||||
@@ -1389,9 +1703,11 @@ export function useAdminFiles() {
|
||||
openTextPreview,
|
||||
refreshFiles,
|
||||
resetFilters,
|
||||
saveCurrentViewPreset,
|
||||
setHealthFilter,
|
||||
setStatusFilter,
|
||||
setTypeFilter,
|
||||
updateSelectedViewPreset,
|
||||
updateDetailMetadata,
|
||||
toggleCurrentPageSelection,
|
||||
toggleFileSelection
|
||||
|
||||
@@ -418,6 +418,24 @@ export default {
|
||||
statusLabel: 'Status',
|
||||
typeLabel: 'Type',
|
||||
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',
|
||||
active: 'Available',
|
||||
expired: 'Expired',
|
||||
|
||||
@@ -416,6 +416,24 @@ export default {
|
||||
statusLabel: '状态',
|
||||
typeLabel: '类型',
|
||||
healthLabel: '健康',
|
||||
viewPreset: '视图',
|
||||
viewPresetCustom: '自定义筛选',
|
||||
viewPresetAll: '全部文件',
|
||||
viewPresetAttention: '需关注',
|
||||
viewPresetExpiringSoon: '即将过期',
|
||||
viewPresetStorageIssue: '存储异常',
|
||||
viewPresetNeverRetrieved: '未取件',
|
||||
viewPresetPermanent: '永久有效',
|
||||
saveViewPreset: '保存视图',
|
||||
updateViewPreset: '覆盖视图',
|
||||
deleteViewPreset: '删除视图',
|
||||
viewPresetNamePrompt: '输入视图名称',
|
||||
viewPresetSaveSuccess: '视图已保存',
|
||||
viewPresetSaveFailed: '视图保存失败',
|
||||
viewPresetDeleteConfirm: '确认删除视图“{name}”?',
|
||||
viewPresetDeleteSuccess: '视图已删除',
|
||||
viewPresetDeleteFailed: '视图删除失败',
|
||||
viewPresetLoadFailed: '视图预设加载失败,已使用内置视图',
|
||||
all: '全部',
|
||||
active: '可取件',
|
||||
expired: '已过期',
|
||||
|
||||
@@ -12,6 +12,9 @@ import type {
|
||||
AdminFileMetadataRequest,
|
||||
AdminFilePolicyActionRequest,
|
||||
AdminFilePreviewResponse,
|
||||
AdminFileViewPreset,
|
||||
AdminFileViewPresetRequest,
|
||||
AdminFileViewPresetsResponse,
|
||||
ApiResponse,
|
||||
ChunkUploadCompleteRequest,
|
||||
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(
|
||||
data: AdminBatchUpdateFilesRequest
|
||||
): 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(
|
||||
id: number
|
||||
): Promise<{ data: Blob; headers: Record<string, string> }> {
|
||||
|
||||
@@ -102,6 +102,43 @@ export interface AdminFileListParams {
|
||||
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 {
|
||||
id: number | null
|
||||
code: string
|
||||
|
||||
@@ -159,6 +159,64 @@
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</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>
|
||||
</section>
|
||||
|
||||
@@ -1178,6 +1236,9 @@ const {
|
||||
isDetailPolicyActionRunning,
|
||||
isPreviewLoading,
|
||||
isSaving,
|
||||
isViewPresetDeleting,
|
||||
isViewPresetLoading,
|
||||
isViewPresetSaving,
|
||||
batchEditForm,
|
||||
batchPolicyActionOptions,
|
||||
detailPolicyActionOptions,
|
||||
@@ -1191,6 +1252,7 @@ const {
|
||||
selectedFileDetail,
|
||||
selectedCount,
|
||||
selectedFileIds,
|
||||
selectedViewPresetId,
|
||||
storageUsedText,
|
||||
summary,
|
||||
showBatchEditModal,
|
||||
@@ -1199,6 +1261,8 @@ const {
|
||||
editForm,
|
||||
showTextPreview,
|
||||
previewText,
|
||||
viewPresets,
|
||||
applyViewPreset,
|
||||
closeBatchEditModal,
|
||||
closeEditModal,
|
||||
closeFileDetail,
|
||||
@@ -1209,6 +1273,7 @@ const {
|
||||
clearSelection,
|
||||
deleteFile,
|
||||
deleteSelectedFiles,
|
||||
deleteSelectedViewPreset,
|
||||
downloadFile,
|
||||
exportPreviewText,
|
||||
handlePageChange,
|
||||
@@ -1217,6 +1282,7 @@ const {
|
||||
applySelectedPolicyAction,
|
||||
handleBatchUpdate,
|
||||
handleUpdate,
|
||||
loadViewPresets,
|
||||
loadFiles,
|
||||
openBatchEditModal,
|
||||
openEditModal,
|
||||
@@ -1224,9 +1290,11 @@ const {
|
||||
openTextPreview,
|
||||
refreshFiles,
|
||||
resetFilters,
|
||||
saveCurrentViewPreset,
|
||||
setHealthFilter,
|
||||
setStatusFilter,
|
||||
setTypeFilter,
|
||||
updateSelectedViewPreset,
|
||||
updateDetailMetadata,
|
||||
toggleCurrentPageSelection,
|
||||
toggleFileSelection
|
||||
@@ -1364,6 +1432,11 @@ const detailInsightReasonLabels = computed(() => {
|
||||
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 = () => {
|
||||
if (!selectedFileDetail.value) return
|
||||
|
||||
@@ -1479,6 +1552,24 @@ const handleResetFilters = async () => {
|
||||
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<
|
||||
{ value: AdminBatchEditMode; label: string; icon: Component }[]
|
||||
>(() => [
|
||||
@@ -1626,6 +1717,7 @@ watch(
|
||||
|
||||
onMounted(() => {
|
||||
params.value.health = getRouteHealthFilter()
|
||||
void loadViewPresets()
|
||||
void loadFiles()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user