feat: add admin batch policy editor
This commit is contained in:
@@ -3,7 +3,11 @@ import { useI18n } from 'vue-i18n'
|
||||
import { FileService } from '@/services'
|
||||
import { useAlertStore } from '@/stores/alertStore'
|
||||
import type {
|
||||
AdminBatchEditForm,
|
||||
AdminBatchDeleteFilesResponse,
|
||||
AdminBatchUpdateFilesRequest,
|
||||
AdminBatchUpdateFilesResponse,
|
||||
AdminFilePatchPayload,
|
||||
AdminFileListParams,
|
||||
AdminFileStatusFilter,
|
||||
AdminFileSummary,
|
||||
@@ -46,11 +50,19 @@ type ErrorWithResponse = {
|
||||
}
|
||||
}
|
||||
|
||||
const isLegacyBatchDeleteUnavailable = (error: unknown) => {
|
||||
const isLegacyBatchActionUnavailable = (error: unknown) => {
|
||||
const status = (error as ErrorWithResponse)?.response?.status
|
||||
return status === 404 || status === 405
|
||||
}
|
||||
|
||||
const legacyForeverExpiresAt = '2099-12-31T23:59'
|
||||
|
||||
const emptyBatchEditForm = (): AdminBatchEditForm => ({
|
||||
mode: 'expiresAt',
|
||||
expired_at: '',
|
||||
expired_count: null
|
||||
})
|
||||
|
||||
export function useAdminFiles() {
|
||||
const { t } = useI18n()
|
||||
const alertStore = useAlertStore()
|
||||
@@ -80,6 +92,8 @@ export function useAdminFiles() {
|
||||
expired_at: '',
|
||||
expired_count: null
|
||||
})
|
||||
const showBatchEditModal = ref(false)
|
||||
const batchEditForm = ref<AdminBatchEditForm>(emptyBatchEditForm())
|
||||
|
||||
const showTextPreview = ref(false)
|
||||
const previewText = ref('')
|
||||
@@ -89,6 +103,8 @@ export function useAdminFiles() {
|
||||
const downloadingFileId = ref<number | null>(null)
|
||||
const selectedFileIds = ref<Set<number>>(new Set())
|
||||
const isBatchDeleting = ref(false)
|
||||
const isBatchUpdating = ref(false)
|
||||
const isBatchActionRunning = computed(() => isBatchDeleting.value || isBatchUpdating.value)
|
||||
const totalPages = computed(() => Math.max(Math.ceil(params.value.total / params.value.size), 1))
|
||||
const storageUsedText = computed(() => formatFileSize(summary.value.storageUsed))
|
||||
const currentPageSelectedCount = computed(
|
||||
@@ -233,6 +249,72 @@ export function useAdminFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
const resetBatchEditForm = () => {
|
||||
batchEditForm.value = emptyBatchEditForm()
|
||||
}
|
||||
|
||||
const openBatchEditModal = () => {
|
||||
if (!hasSelectedFiles.value || isBatchActionRunning.value) return
|
||||
|
||||
resetBatchEditForm()
|
||||
showBatchEditModal.value = true
|
||||
}
|
||||
|
||||
const closeBatchEditModal = (force = false) => {
|
||||
if (isBatchUpdating.value && !force) return
|
||||
|
||||
showBatchEditModal.value = false
|
||||
resetBatchEditForm()
|
||||
}
|
||||
|
||||
const buildBatchUpdatePayload = (ids: number[]): AdminBatchUpdateFilesRequest | null => {
|
||||
const form = batchEditForm.value
|
||||
|
||||
if (form.mode === 'expiresAt') {
|
||||
if (!form.expired_at) return null
|
||||
return {
|
||||
ids,
|
||||
expired_at: form.expired_at
|
||||
}
|
||||
}
|
||||
|
||||
if (form.mode === 'downloadLimit') {
|
||||
if (form.expired_count === null || !Number.isFinite(form.expired_count)) return null
|
||||
return {
|
||||
ids,
|
||||
expired_count: form.expired_count
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ids,
|
||||
clearExpiredAt: true,
|
||||
clear_expired_at: true
|
||||
}
|
||||
}
|
||||
|
||||
const buildLegacyBatchPatchPayload = (
|
||||
id: number,
|
||||
payload: AdminBatchUpdateFilesRequest
|
||||
): AdminFilePatchPayload => {
|
||||
if (payload.clearExpiredAt || payload.clear_expired_at) {
|
||||
return {
|
||||
id,
|
||||
expired_at: legacyForeverExpiresAt,
|
||||
expired_count: -1
|
||||
}
|
||||
}
|
||||
|
||||
const patchPayload: AdminFilePatchPayload = { id }
|
||||
if (payload.expired_at !== undefined) {
|
||||
patchPayload.expired_at = payload.expired_at
|
||||
}
|
||||
if (payload.expired_count !== undefined) {
|
||||
patchPayload.expired_count = payload.expired_count
|
||||
}
|
||||
return patchPayload
|
||||
}
|
||||
|
||||
const loadFiles = async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
@@ -351,7 +433,7 @@ export function useAdminFiles() {
|
||||
try {
|
||||
return await FileService.deleteAdminFiles(ids)
|
||||
} catch (error: unknown) {
|
||||
if (!isLegacyBatchDeleteUnavailable(error)) {
|
||||
if (!isLegacyBatchActionUnavailable(error)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -369,8 +451,36 @@ export function useAdminFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
const updateAdminFilesWithFallback = async (
|
||||
ids: number[],
|
||||
payload: AdminBatchUpdateFilesRequest
|
||||
): Promise<ApiResponse<AdminBatchUpdateFilesResponse>> => {
|
||||
try {
|
||||
return await FileService.updateAdminFiles(payload)
|
||||
} catch (error: unknown) {
|
||||
if (!isLegacyBatchActionUnavailable(error)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
ids.map((id) => FileService.updateFile(buildLegacyBatchPatchPayload(id, payload)))
|
||||
)
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
detail: {
|
||||
updatedCount: ids.length,
|
||||
updated_count: ids.length,
|
||||
updated: ids,
|
||||
missing: [],
|
||||
failed: []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deleteSelectedFiles = async () => {
|
||||
if (!hasSelectedFiles.value || isBatchDeleting.value) return
|
||||
if (!hasSelectedFiles.value || isBatchActionRunning.value) return
|
||||
|
||||
const ids = Array.from(selectedFileIds.value)
|
||||
if (!window.confirm(t('fileManage.batchDeleteConfirm', { count: ids.length }))) {
|
||||
@@ -409,6 +519,49 @@ export function useAdminFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleBatchUpdate = async () => {
|
||||
if (!hasSelectedFiles.value || isBatchActionRunning.value) return
|
||||
|
||||
const ids = Array.from(selectedFileIds.value)
|
||||
const payload = buildBatchUpdatePayload(ids)
|
||||
if (!payload) {
|
||||
alertStore.showAlert(t('fileManage.batchUpdateNoFields'), 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
if (!window.confirm(t('fileManage.batchUpdateConfirm', { count: ids.length }))) {
|
||||
return
|
||||
}
|
||||
|
||||
isBatchUpdating.value = true
|
||||
try {
|
||||
const response = await updateAdminFilesWithFallback(ids, payload)
|
||||
const detail = response.detail
|
||||
const updatedCount = detail?.updatedCount ?? detail?.updated_count ?? ids.length
|
||||
const failedCount = detail?.failedCount ?? detail?.failed_count ?? 0
|
||||
|
||||
clearSelection()
|
||||
await loadFiles()
|
||||
closeBatchEditModal(true)
|
||||
alertStore.showAlert(
|
||||
t(
|
||||
failedCount > 0
|
||||
? 'fileManage.batchUpdatePartialSuccess'
|
||||
: 'fileManage.batchUpdateSuccess',
|
||||
{
|
||||
count: updatedCount,
|
||||
failed: failedCount
|
||||
}
|
||||
),
|
||||
failedCount > 0 ? 'warning' : 'success'
|
||||
)
|
||||
} catch (error: unknown) {
|
||||
alertStore.showAlert(getErrorMessage(error, t('fileManage.batchUpdateFailed')), 'error')
|
||||
} finally {
|
||||
isBatchUpdating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openTextPreview = async (file: AdminFileViewItem) => {
|
||||
if (!file.isTextFile || isPreviewLoading.value) return
|
||||
|
||||
@@ -491,10 +644,13 @@ export function useAdminFiles() {
|
||||
hasActiveFilters,
|
||||
isLoading,
|
||||
isAllCurrentPageSelected,
|
||||
isBatchActionRunning,
|
||||
isBatchDeleting,
|
||||
isBatchUpdating,
|
||||
isCurrentPagePartiallySelected,
|
||||
isPreviewLoading,
|
||||
isSaving,
|
||||
batchEditForm,
|
||||
downloadingFileId,
|
||||
hasSelectedFiles,
|
||||
params,
|
||||
@@ -504,12 +660,14 @@ export function useAdminFiles() {
|
||||
selectedFileIds,
|
||||
storageUsedText,
|
||||
summary,
|
||||
showBatchEditModal,
|
||||
showEditModal,
|
||||
editForm,
|
||||
showTextPreview,
|
||||
previewText,
|
||||
totalPages,
|
||||
closeEditModal,
|
||||
closeBatchEditModal,
|
||||
closeTextPreview,
|
||||
copyText,
|
||||
clearSelection,
|
||||
@@ -519,8 +677,10 @@ export function useAdminFiles() {
|
||||
exportPreviewText,
|
||||
handlePageChange,
|
||||
handleSearch,
|
||||
handleBatchUpdate,
|
||||
handleUpdate,
|
||||
loadFiles,
|
||||
openBatchEditModal,
|
||||
openEditModal,
|
||||
openTextPreview,
|
||||
refreshFiles,
|
||||
|
||||
@@ -427,6 +427,20 @@ export default {
|
||||
batchDeleteSuccess: 'Deleted {count} files',
|
||||
batchDeletePartialSuccess: 'Deleted {count} files, {failed} failed',
|
||||
batchDeleteFailed: 'Batch delete failed',
|
||||
batchEdit: 'Batch Edit',
|
||||
batchEditTitle: 'Batch Edit File Policy',
|
||||
batchEditSelected: 'Update the selected {count} files',
|
||||
batchEditMode: 'Update Mode',
|
||||
batchEditExpiresAt: 'Set Expiration Time',
|
||||
batchEditDownloadLimit: 'Set Retrieval Limit',
|
||||
batchEditForever: 'Make Permanent',
|
||||
batchEditForeverHint: 'Clear the expiration time and make retrievals unlimited.',
|
||||
batchUpdateConfirm: 'Update the selected {count} files?',
|
||||
batchUpdateSuccess: 'Updated {count} files',
|
||||
batchUpdatePartialSuccess: 'Updated {count} files, {failed} failed',
|
||||
batchUpdateFailed: 'Batch update failed',
|
||||
batchUpdateNoFields: 'Choose a policy to update',
|
||||
applyBatchEdit: 'Apply Changes',
|
||||
headers: {
|
||||
select: 'Select',
|
||||
code: 'Retrieve Code',
|
||||
|
||||
@@ -425,6 +425,20 @@ export default {
|
||||
batchDeleteSuccess: '已删除 {count} 个文件',
|
||||
batchDeletePartialSuccess: '已删除 {count} 个文件,{failed} 个失败',
|
||||
batchDeleteFailed: '批量删除失败',
|
||||
batchEdit: '批量编辑',
|
||||
batchEditTitle: '批量编辑文件策略',
|
||||
batchEditSelected: '将更新选中的 {count} 个文件',
|
||||
batchEditMode: '更新方式',
|
||||
batchEditExpiresAt: '设置过期时间',
|
||||
batchEditDownloadLimit: '设置取件次数',
|
||||
batchEditForever: '设为永久有效',
|
||||
batchEditForeverHint: '清空过期时间,并将取件次数设为不限。',
|
||||
batchUpdateConfirm: '确认更新选中的 {count} 个文件?',
|
||||
batchUpdateSuccess: '已更新 {count} 个文件',
|
||||
batchUpdatePartialSuccess: '已更新 {count} 个文件,{failed} 个失败',
|
||||
batchUpdateFailed: '批量更新失败',
|
||||
batchUpdateNoFields: '请选择要更新的策略',
|
||||
applyBatchEdit: '应用更改',
|
||||
headers: {
|
||||
select: '选择',
|
||||
code: '取件码',
|
||||
|
||||
+10
-1
@@ -2,6 +2,9 @@ import api, { rawApiClient } from './client'
|
||||
import { multipartUploadConfig } from './shared'
|
||||
import type {
|
||||
AdminBatchDeleteFilesResponse,
|
||||
AdminBatchUpdateFilesRequest,
|
||||
AdminBatchUpdateFilesResponse,
|
||||
AdminFilePatchPayload,
|
||||
AdminFileListParams,
|
||||
AdminFilePreviewResponse,
|
||||
ApiResponse,
|
||||
@@ -125,10 +128,16 @@ export class FileService {
|
||||
return api.get('/admin/file/list', { params })
|
||||
}
|
||||
|
||||
static async updateFile(data: FileEditForm): Promise<ApiResponse> {
|
||||
static async updateFile(data: FileEditForm | AdminFilePatchPayload): Promise<ApiResponse> {
|
||||
return api.patch('/admin/file/update', data)
|
||||
}
|
||||
|
||||
static async updateAdminFiles(
|
||||
data: AdminBatchUpdateFilesRequest
|
||||
): Promise<ApiResponse<AdminBatchUpdateFilesResponse>> {
|
||||
return api.patch('/admin/file/batch-update', data)
|
||||
}
|
||||
|
||||
static async deleteAdminFile(id: number): Promise<ApiResponse> {
|
||||
return api.delete('/admin/file/delete', {
|
||||
data: { id }
|
||||
|
||||
@@ -83,6 +83,15 @@ export interface FileEditForm {
|
||||
expired_count: number | null
|
||||
}
|
||||
|
||||
export interface AdminFilePatchPayload {
|
||||
id: number
|
||||
code?: string
|
||||
prefix?: string
|
||||
suffix?: string
|
||||
expired_at?: string | null
|
||||
expired_count?: number | null
|
||||
}
|
||||
|
||||
export interface FileListResponse {
|
||||
data: FileListItem[]
|
||||
total: number
|
||||
@@ -130,6 +139,43 @@ export interface AdminBatchDeleteFilesResponse {
|
||||
failed?: AdminBatchDeleteFileFailure[]
|
||||
}
|
||||
|
||||
export interface AdminBatchUpdateFileFailure {
|
||||
id: number
|
||||
reason: string
|
||||
}
|
||||
|
||||
export interface AdminBatchUpdateFilesRequest {
|
||||
ids: number[]
|
||||
expired_at?: string | null
|
||||
expired_count?: number | null
|
||||
clearExpiredAt?: boolean
|
||||
clear_expired_at?: boolean
|
||||
}
|
||||
|
||||
export interface AdminBatchUpdateFilesResponse {
|
||||
requestedCount?: number
|
||||
requested_count?: number
|
||||
uniqueCount?: number
|
||||
unique_count?: number
|
||||
updatedCount?: number
|
||||
updated_count?: number
|
||||
missingCount?: number
|
||||
missing_count?: number
|
||||
failedCount?: number
|
||||
failed_count?: number
|
||||
updated?: number[]
|
||||
missing?: number[]
|
||||
failed?: AdminBatchUpdateFileFailure[]
|
||||
}
|
||||
|
||||
export type AdminBatchEditMode = 'expiresAt' | 'downloadLimit' | 'forever'
|
||||
|
||||
export interface AdminBatchEditForm {
|
||||
mode: AdminBatchEditMode
|
||||
expired_at: string
|
||||
expired_count: number | null
|
||||
}
|
||||
|
||||
export interface FileUploadResponse {
|
||||
code: string
|
||||
name: string
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
type="checkbox"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
||||
:checked="isAllCurrentPageSelected"
|
||||
:disabled="isBatchDeleting"
|
||||
:disabled="isBatchActionRunning"
|
||||
:indeterminate="isCurrentPagePartiallySelected"
|
||||
@change="toggleCurrentPageSelection"
|
||||
/>
|
||||
@@ -172,7 +172,7 @@
|
||||
v-if="hasSelectedFiles"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="isBatchDeleting"
|
||||
:disabled="isBatchActionRunning"
|
||||
@click="clearSelection"
|
||||
>
|
||||
<template #icon>
|
||||
@@ -180,10 +180,22 @@
|
||||
</template>
|
||||
{{ t('fileManage.clearSelection') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
:disabled="!hasSelectedFiles || isBatchDeleting"
|
||||
:loading="isBatchUpdating"
|
||||
@click="openBatchEditModal"
|
||||
>
|
||||
<template #icon>
|
||||
<ClockIcon class="mr-2 h-4 w-4" />
|
||||
</template>
|
||||
{{ t('fileManage.batchEdit') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
variant="danger"
|
||||
size="sm"
|
||||
:disabled="!hasSelectedFiles"
|
||||
:disabled="!hasSelectedFiles || isBatchUpdating"
|
||||
:loading="isBatchDeleting"
|
||||
@click="deleteSelectedFiles"
|
||||
>
|
||||
@@ -262,7 +274,7 @@
|
||||
type="checkbox"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
||||
:checked="selectedFileIds.has(file.id)"
|
||||
:disabled="isBatchDeleting"
|
||||
:disabled="isBatchActionRunning"
|
||||
:aria-label="t('fileManage.selectFile', { name: file.displayName })"
|
||||
@change="toggleFileSelection(file.id)"
|
||||
/>
|
||||
@@ -467,6 +479,95 @@
|
||||
</template>
|
||||
</BaseModal>
|
||||
|
||||
<BaseModal :show="showBatchEditModal" size="lg" @close="closeBatchEditModal()">
|
||||
<template #header>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="p-2 rounded-lg" :class="[isDarkMode ? 'bg-indigo-500/10' : 'bg-indigo-50']">
|
||||
<ClockIcon
|
||||
class="w-5 h-5"
|
||||
:class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-600']"
|
||||
/>
|
||||
</div>
|
||||
<h3
|
||||
class="text-xl font-semibold leading-6"
|
||||
:class="[isDarkMode ? 'text-white' : 'text-gray-900']"
|
||||
>
|
||||
{{ t('fileManage.batchEditTitle') }}
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="space-y-5">
|
||||
<div
|
||||
class="rounded-lg border px-4 py-3 text-sm"
|
||||
:class="[
|
||||
isDarkMode
|
||||
? 'border-indigo-500/20 bg-indigo-500/10 text-indigo-200'
|
||||
: 'border-indigo-100 bg-indigo-50 text-indigo-700'
|
||||
]"
|
||||
>
|
||||
{{ t('fileManage.batchEditSelected', { count: selectedCount }) }}
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm font-medium" :class="[primaryTextClass]">
|
||||
{{ t('fileManage.batchEditMode') }}
|
||||
</p>
|
||||
<div class="grid gap-2 sm:grid-cols-3">
|
||||
<button
|
||||
v-for="option in batchEditModeOptions"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
class="flex min-h-12 items-center justify-center rounded-lg border px-3 py-2 text-sm font-medium transition-colors"
|
||||
:class="getBatchEditModeClass(option.value)"
|
||||
:disabled="isBatchUpdating"
|
||||
@click="batchEditForm.mode = option.value"
|
||||
>
|
||||
<component :is="option.icon" class="mr-2 h-4 w-4" />
|
||||
<span>{{ option.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FileEditField
|
||||
v-if="batchEditForm.mode === 'expiresAt'"
|
||||
v-model="batchEditForm.expired_at"
|
||||
type="datetime-local"
|
||||
:label="t('fileManage.batchEditExpiresAt')"
|
||||
/>
|
||||
<FileEditField
|
||||
v-else-if="batchEditForm.mode === 'downloadLimit'"
|
||||
v-model="batchEditForm.expired_count"
|
||||
type="number"
|
||||
:label="t('fileManage.batchEditDownloadLimit')"
|
||||
:placeholder="t('fileManage.form.downloadLimitPlaceholder')"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="rounded-lg border px-4 py-3 text-sm"
|
||||
:class="[
|
||||
isDarkMode
|
||||
? 'border-gray-700 bg-gray-700/50 text-gray-300'
|
||||
: 'border-gray-200 bg-gray-50 text-gray-600'
|
||||
]"
|
||||
>
|
||||
{{ t('fileManage.batchEditForeverHint') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<BaseButton variant="secondary" :disabled="isBatchUpdating" @click="closeBatchEditModal()">
|
||||
{{ t('common.cancel') }}
|
||||
</BaseButton>
|
||||
<BaseButton :loading="isBatchUpdating" @click="handleBatchUpdate">
|
||||
<template #icon>
|
||||
<CheckIcon class="w-4 h-4 mr-2" />
|
||||
</template>
|
||||
{{ t('fileManage.applyBatchEdit') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
</BaseModal>
|
||||
|
||||
<BaseModal :show="showTextPreview" size="lg" @close="closeTextPreview">
|
||||
<template #header>
|
||||
<div class="flex items-center space-x-3">
|
||||
@@ -538,9 +639,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { computed, onMounted, type Component } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type {
|
||||
AdminBatchEditMode,
|
||||
AdminFileSortBy,
|
||||
AdminFileSortOrder,
|
||||
AdminFileStatusFilter,
|
||||
@@ -593,10 +695,13 @@ const {
|
||||
hasLoadError,
|
||||
isLoading,
|
||||
isAllCurrentPageSelected,
|
||||
isBatchActionRunning,
|
||||
isBatchDeleting,
|
||||
isBatchUpdating,
|
||||
isCurrentPagePartiallySelected,
|
||||
isPreviewLoading,
|
||||
isSaving,
|
||||
batchEditForm,
|
||||
downloadingFileId,
|
||||
hasSelectedFiles,
|
||||
params,
|
||||
@@ -606,10 +711,12 @@ const {
|
||||
selectedFileIds,
|
||||
storageUsedText,
|
||||
summary,
|
||||
showBatchEditModal,
|
||||
showEditModal,
|
||||
editForm,
|
||||
showTextPreview,
|
||||
previewText,
|
||||
closeBatchEditModal,
|
||||
closeEditModal,
|
||||
closeTextPreview,
|
||||
copyText,
|
||||
@@ -620,8 +727,10 @@ const {
|
||||
exportPreviewText,
|
||||
handlePageChange,
|
||||
handleSearch,
|
||||
handleBatchUpdate,
|
||||
handleUpdate,
|
||||
loadFiles,
|
||||
openBatchEditModal,
|
||||
openEditModal,
|
||||
openTextPreview,
|
||||
refreshFiles,
|
||||
@@ -701,6 +810,26 @@ const sortOrderOptions = computed<{ value: AdminFileSortOrder; label: string }[]
|
||||
{ value: 'asc', label: t('fileManage.sort.asc') }
|
||||
])
|
||||
|
||||
const batchEditModeOptions = computed<
|
||||
{ value: AdminBatchEditMode; label: string; icon: Component }[]
|
||||
>(() => [
|
||||
{
|
||||
value: 'expiresAt',
|
||||
label: t('fileManage.batchEditExpiresAt'),
|
||||
icon: ClockIcon
|
||||
},
|
||||
{
|
||||
value: 'downloadLimit',
|
||||
label: t('fileManage.batchEditDownloadLimit'),
|
||||
icon: DownloadIcon
|
||||
},
|
||||
{
|
||||
value: 'forever',
|
||||
label: t('fileManage.batchEditForever'),
|
||||
icon: CheckIcon
|
||||
}
|
||||
])
|
||||
|
||||
const getPillClass = (active: boolean) => {
|
||||
if (active) return 'bg-indigo-600 text-white'
|
||||
return isDarkMode.value
|
||||
@@ -713,6 +842,18 @@ const getStatusFilterClass = (value: AdminFileStatusFilter) =>
|
||||
|
||||
const getTypeFilterClass = (value: AdminFileTypeFilter) => getPillClass(params.value.type === value)
|
||||
|
||||
const getBatchEditModeClass = (value: AdminBatchEditMode) => {
|
||||
if (batchEditForm.value.mode === value) {
|
||||
return isDarkMode.value
|
||||
? 'border-indigo-500 bg-indigo-500/20 text-indigo-200'
|
||||
: 'border-indigo-500 bg-indigo-50 text-indigo-700'
|
||||
}
|
||||
|
||||
return isDarkMode.value
|
||||
? 'border-gray-700 bg-gray-700/50 text-gray-300 hover:border-gray-600 hover:bg-gray-700'
|
||||
: 'border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:bg-gray-50'
|
||||
}
|
||||
|
||||
const getTypeLabel = (file: AdminFileViewItem) => {
|
||||
if (file.isChunkedFile) return t('fileManage.chunkedType')
|
||||
return file.isTextFile ? t('fileManage.textType') : t('fileManage.fileType')
|
||||
|
||||
Reference in New Issue
Block a user