feat: add admin batch file selection
This commit is contained in:
@@ -3,11 +3,13 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { FileService } from '@/services'
|
import { FileService } from '@/services'
|
||||||
import { useAlertStore } from '@/stores/alertStore'
|
import { useAlertStore } from '@/stores/alertStore'
|
||||||
import type {
|
import type {
|
||||||
|
AdminBatchDeleteFilesResponse,
|
||||||
AdminFileListParams,
|
AdminFileListParams,
|
||||||
AdminFileStatusFilter,
|
AdminFileStatusFilter,
|
||||||
AdminFileSummary,
|
AdminFileSummary,
|
||||||
AdminFileTypeFilter,
|
AdminFileTypeFilter,
|
||||||
AdminFileViewItem,
|
AdminFileViewItem,
|
||||||
|
ApiResponse,
|
||||||
FileEditForm,
|
FileEditForm,
|
||||||
FileListItem
|
FileListItem
|
||||||
} from '@/types'
|
} from '@/types'
|
||||||
@@ -38,6 +40,17 @@ const isExpiredByDate = (value: string | null | undefined) => {
|
|||||||
return Number.isFinite(timestamp) && timestamp < Date.now()
|
return Number.isFinite(timestamp) && timestamp < Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrorWithResponse = {
|
||||||
|
response?: {
|
||||||
|
status?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLegacyBatchDeleteUnavailable = (error: unknown) => {
|
||||||
|
const status = (error as ErrorWithResponse)?.response?.status
|
||||||
|
return status === 404 || status === 405
|
||||||
|
}
|
||||||
|
|
||||||
export function useAdminFiles() {
|
export function useAdminFiles() {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const alertStore = useAlertStore()
|
const alertStore = useAlertStore()
|
||||||
@@ -74,8 +87,21 @@ export function useAdminFiles() {
|
|||||||
const previewMetaText = ref('')
|
const previewMetaText = ref('')
|
||||||
const isPreviewLoading = ref(false)
|
const isPreviewLoading = ref(false)
|
||||||
const downloadingFileId = ref<number | null>(null)
|
const downloadingFileId = ref<number | null>(null)
|
||||||
|
const selectedFileIds = ref<Set<number>>(new Set())
|
||||||
|
const isBatchDeleting = ref(false)
|
||||||
const totalPages = computed(() => Math.max(Math.ceil(params.value.total / params.value.size), 1))
|
const totalPages = computed(() => Math.max(Math.ceil(params.value.total / params.value.size), 1))
|
||||||
const storageUsedText = computed(() => formatFileSize(summary.value.storageUsed))
|
const storageUsedText = computed(() => formatFileSize(summary.value.storageUsed))
|
||||||
|
const currentPageSelectedCount = computed(
|
||||||
|
() => tableData.value.filter((file) => selectedFileIds.value.has(file.id)).length
|
||||||
|
)
|
||||||
|
const selectedCount = computed(() => selectedFileIds.value.size)
|
||||||
|
const hasSelectedFiles = computed(() => selectedCount.value > 0)
|
||||||
|
const isAllCurrentPageSelected = computed(
|
||||||
|
() => tableData.value.length > 0 && currentPageSelectedCount.value === tableData.value.length
|
||||||
|
)
|
||||||
|
const isCurrentPagePartiallySelected = computed(
|
||||||
|
() => currentPageSelectedCount.value > 0 && !isAllCurrentPageSelected.value
|
||||||
|
)
|
||||||
|
|
||||||
const requestParams = computed<AdminFileListParams>(() => ({
|
const requestParams = computed<AdminFileListParams>(() => ({
|
||||||
page: params.value.page,
|
page: params.value.page,
|
||||||
@@ -166,6 +192,36 @@ export function useAdminFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const syncSelectedFilesWithCurrentPage = () => {
|
||||||
|
const visibleIds = new Set(tableData.value.map((file) => file.id))
|
||||||
|
selectedFileIds.value = new Set(
|
||||||
|
Array.from(selectedFileIds.value).filter((id) => visibleIds.has(id))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearSelection = () => {
|
||||||
|
selectedFileIds.value = new Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleFileSelection = (id: number) => {
|
||||||
|
const nextSelectedIds = new Set(selectedFileIds.value)
|
||||||
|
if (nextSelectedIds.has(id)) {
|
||||||
|
nextSelectedIds.delete(id)
|
||||||
|
} else {
|
||||||
|
nextSelectedIds.add(id)
|
||||||
|
}
|
||||||
|
selectedFileIds.value = nextSelectedIds
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleCurrentPageSelection = () => {
|
||||||
|
if (isAllCurrentPageSelected.value) {
|
||||||
|
clearSelection()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedFileIds.value = new Set(tableData.value.map((file) => file.id))
|
||||||
|
}
|
||||||
|
|
||||||
const resetEditForm = () => {
|
const resetEditForm = () => {
|
||||||
editForm.value = {
|
editForm.value = {
|
||||||
id: null,
|
id: null,
|
||||||
@@ -187,6 +243,7 @@ export function useAdminFiles() {
|
|||||||
tableData.value = res.detail.data.map(createFileViewItem)
|
tableData.value = res.detail.data.map(createFileViewItem)
|
||||||
params.value.total = res.detail.total
|
params.value.total = res.detail.total
|
||||||
summary.value = res.detail.summary || buildFallbackSummary(tableData.value, res.detail.total)
|
summary.value = res.detail.summary || buildFallbackSummary(tableData.value, res.detail.total)
|
||||||
|
syncSelectedFilesWithCurrentPage()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
hasLoadError.value = true
|
hasLoadError.value = true
|
||||||
alertStore.showAlert(
|
alertStore.showAlert(
|
||||||
@@ -214,6 +271,7 @@ export function useAdminFiles() {
|
|||||||
params.value.type = 'all'
|
params.value.type = 'all'
|
||||||
params.value.sortBy = 'created_at'
|
params.value.sortBy = 'created_at'
|
||||||
params.value.sortOrder = 'desc'
|
params.value.sortOrder = 'desc'
|
||||||
|
clearSelection()
|
||||||
await loadFiles()
|
await loadFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,12 +337,78 @@ export function useAdminFiles() {
|
|||||||
if (tableData.value.length === 1 && params.value.page > 1) {
|
if (tableData.value.length === 1 && params.value.page > 1) {
|
||||||
params.value.page -= 1
|
params.value.page -= 1
|
||||||
}
|
}
|
||||||
|
selectedFileIds.value.delete(id)
|
||||||
|
selectedFileIds.value = new Set(selectedFileIds.value)
|
||||||
await loadFiles()
|
await loadFiles()
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
alertStore.showAlert(getErrorMessage(error, t('manage.fileManage.deleteFailed')), 'error')
|
alertStore.showAlert(getErrorMessage(error, t('manage.fileManage.deleteFailed')), 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteAdminFilesWithFallback = async (
|
||||||
|
ids: number[]
|
||||||
|
): Promise<ApiResponse<AdminBatchDeleteFilesResponse>> => {
|
||||||
|
try {
|
||||||
|
return await FileService.deleteAdminFiles(ids)
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (!isLegacyBatchDeleteUnavailable(error)) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(ids.map((id) => FileService.deleteAdminFile(id)))
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
detail: {
|
||||||
|
deletedCount: ids.length,
|
||||||
|
deleted_count: ids.length,
|
||||||
|
deleted: ids,
|
||||||
|
missing: [],
|
||||||
|
failed: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSelectedFiles = async () => {
|
||||||
|
if (!hasSelectedFiles.value || isBatchDeleting.value) return
|
||||||
|
|
||||||
|
const ids = Array.from(selectedFileIds.value)
|
||||||
|
if (!window.confirm(t('fileManage.batchDeleteConfirm', { count: ids.length }))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isBatchDeleting.value = true
|
||||||
|
try {
|
||||||
|
const response = await deleteAdminFilesWithFallback(ids)
|
||||||
|
const detail = response.detail
|
||||||
|
const deletedCount = detail?.deletedCount ?? detail?.deleted_count ?? ids.length
|
||||||
|
const failedCount = detail?.failedCount ?? detail?.failed_count ?? 0
|
||||||
|
|
||||||
|
if (tableData.value.length <= deletedCount && params.value.page > 1) {
|
||||||
|
params.value.page -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSelection()
|
||||||
|
await loadFiles()
|
||||||
|
alertStore.showAlert(
|
||||||
|
t(
|
||||||
|
failedCount > 0
|
||||||
|
? 'fileManage.batchDeletePartialSuccess'
|
||||||
|
: 'fileManage.batchDeleteSuccess',
|
||||||
|
{
|
||||||
|
count: deletedCount,
|
||||||
|
failed: failedCount
|
||||||
|
}
|
||||||
|
),
|
||||||
|
failedCount > 0 ? 'warning' : 'success'
|
||||||
|
)
|
||||||
|
} catch (error: unknown) {
|
||||||
|
alertStore.showAlert(getErrorMessage(error, t('fileManage.batchDeleteFailed')), 'error')
|
||||||
|
} finally {
|
||||||
|
isBatchDeleting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const openTextPreview = async (file: AdminFileViewItem) => {
|
const openTextPreview = async (file: AdminFileViewItem) => {
|
||||||
if (!file.isTextFile || isPreviewLoading.value) return
|
if (!file.isTextFile || isPreviewLoading.value) return
|
||||||
|
|
||||||
@@ -366,12 +490,18 @@ export function useAdminFiles() {
|
|||||||
hasLoadError,
|
hasLoadError,
|
||||||
hasActiveFilters,
|
hasActiveFilters,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isAllCurrentPageSelected,
|
||||||
|
isBatchDeleting,
|
||||||
|
isCurrentPagePartiallySelected,
|
||||||
isPreviewLoading,
|
isPreviewLoading,
|
||||||
isSaving,
|
isSaving,
|
||||||
downloadingFileId,
|
downloadingFileId,
|
||||||
|
hasSelectedFiles,
|
||||||
params,
|
params,
|
||||||
previewFile,
|
previewFile,
|
||||||
previewMetaText,
|
previewMetaText,
|
||||||
|
selectedCount,
|
||||||
|
selectedFileIds,
|
||||||
storageUsedText,
|
storageUsedText,
|
||||||
summary,
|
summary,
|
||||||
showEditModal,
|
showEditModal,
|
||||||
@@ -382,7 +512,9 @@ export function useAdminFiles() {
|
|||||||
closeEditModal,
|
closeEditModal,
|
||||||
closeTextPreview,
|
closeTextPreview,
|
||||||
copyText,
|
copyText,
|
||||||
|
clearSelection,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
|
deleteSelectedFiles,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
exportPreviewText,
|
exportPreviewText,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
@@ -394,6 +526,8 @@ export function useAdminFiles() {
|
|||||||
refreshFiles,
|
refreshFiles,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
setStatusFilter,
|
setStatusFilter,
|
||||||
setTypeFilter
|
setTypeFilter,
|
||||||
|
toggleCurrentPageSelection,
|
||||||
|
toggleFileSelection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,7 +418,17 @@ export default {
|
|||||||
previewComplete: 'Loaded full content, {count} characters',
|
previewComplete: 'Loaded full content, {count} characters',
|
||||||
previewTruncated: 'Showing {shown} / {total} characters',
|
previewTruncated: 'Showing {shown} / {total} characters',
|
||||||
previewFallback: 'Preview endpoint unavailable; using text from the list',
|
previewFallback: 'Preview endpoint unavailable; using text from the list',
|
||||||
|
selectCurrentPage: 'Select current page',
|
||||||
|
selectedCount: '{count} selected',
|
||||||
|
selectFile: 'Select {name}',
|
||||||
|
clearSelection: 'Clear Selection',
|
||||||
|
batchDelete: 'Batch Delete',
|
||||||
|
batchDeleteConfirm: 'Delete the selected {count} files? This action cannot be undone.',
|
||||||
|
batchDeleteSuccess: 'Deleted {count} files',
|
||||||
|
batchDeletePartialSuccess: 'Deleted {count} files, {failed} failed',
|
||||||
|
batchDeleteFailed: 'Batch delete failed',
|
||||||
headers: {
|
headers: {
|
||||||
|
select: 'Select',
|
||||||
code: 'Retrieve Code',
|
code: 'Retrieve Code',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
type: 'Type',
|
type: 'Type',
|
||||||
|
|||||||
@@ -416,7 +416,17 @@ export default {
|
|||||||
previewComplete: '已加载完整内容,共 {count} 个字符',
|
previewComplete: '已加载完整内容,共 {count} 个字符',
|
||||||
previewTruncated: '已显示 {shown} / {total} 个字符',
|
previewTruncated: '已显示 {shown} / {total} 个字符',
|
||||||
previewFallback: '预览接口不可用,已使用列表中的文本内容',
|
previewFallback: '预览接口不可用,已使用列表中的文本内容',
|
||||||
|
selectCurrentPage: '选择当前页',
|
||||||
|
selectedCount: '已选择 {count} 项',
|
||||||
|
selectFile: '选择 {name}',
|
||||||
|
clearSelection: '清空选择',
|
||||||
|
batchDelete: '批量删除',
|
||||||
|
batchDeleteConfirm: '确认删除选中的 {count} 个文件?此操作不可撤销。',
|
||||||
|
batchDeleteSuccess: '已删除 {count} 个文件',
|
||||||
|
batchDeletePartialSuccess: '已删除 {count} 个文件,{failed} 个失败',
|
||||||
|
batchDeleteFailed: '批量删除失败',
|
||||||
headers: {
|
headers: {
|
||||||
|
select: '选择',
|
||||||
code: '取件码',
|
code: '取件码',
|
||||||
name: '名称',
|
name: '名称',
|
||||||
type: '类型',
|
type: '类型',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import api, { rawApiClient } from './client'
|
import api, { rawApiClient } from './client'
|
||||||
import { multipartUploadConfig } from './shared'
|
import { multipartUploadConfig } from './shared'
|
||||||
import type {
|
import type {
|
||||||
|
AdminBatchDeleteFilesResponse,
|
||||||
AdminFileListParams,
|
AdminFileListParams,
|
||||||
AdminFilePreviewResponse,
|
AdminFilePreviewResponse,
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
@@ -134,6 +135,14 @@ export class FileService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async deleteAdminFiles(
|
||||||
|
ids: number[]
|
||||||
|
): Promise<ApiResponse<AdminBatchDeleteFilesResponse>> {
|
||||||
|
return api.delete('/admin/file/batch-delete', {
|
||||||
|
data: { ids }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static async downloadAdminFile(
|
static async downloadAdminFile(
|
||||||
id: number
|
id: number
|
||||||
): Promise<{ data: Blob; headers: Record<string, string> }> {
|
): Promise<{ data: Blob; headers: Record<string, string> }> {
|
||||||
|
|||||||
@@ -109,6 +109,27 @@ export interface AdminFilePreviewResponse {
|
|||||||
expired_at?: string | null
|
expired_at?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AdminBatchDeleteFileFailure {
|
||||||
|
id: number
|
||||||
|
reason: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminBatchDeleteFilesResponse {
|
||||||
|
requestedCount?: number
|
||||||
|
requested_count?: number
|
||||||
|
uniqueCount?: number
|
||||||
|
unique_count?: number
|
||||||
|
deletedCount?: number
|
||||||
|
deleted_count?: number
|
||||||
|
missingCount?: number
|
||||||
|
missing_count?: number
|
||||||
|
failedCount?: number
|
||||||
|
failed_count?: number
|
||||||
|
deleted?: number[]
|
||||||
|
missing?: number[]
|
||||||
|
failed?: AdminBatchDeleteFileFailure[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface FileUploadResponse {
|
export interface FileUploadResponse {
|
||||||
code: string
|
code: string
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
@@ -144,6 +144,57 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
v-if="tableData.length > 0"
|
||||||
|
class="mb-4 flex flex-col gap-3 rounded-lg border px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
|
||||||
|
:class="[panelClass]"
|
||||||
|
>
|
||||||
|
<label class="inline-flex items-center gap-2 text-sm" :class="[primaryTextClass]">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
||||||
|
:checked="isAllCurrentPageSelected"
|
||||||
|
:disabled="isBatchDeleting"
|
||||||
|
:indeterminate="isCurrentPagePartiallySelected"
|
||||||
|
@change="toggleCurrentPageSelection"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
hasSelectedFiles
|
||||||
|
? t('fileManage.selectedCount', { count: selectedCount })
|
||||||
|
: t('fileManage.selectCurrentPage')
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<BaseButton
|
||||||
|
v-if="hasSelectedFiles"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
:disabled="isBatchDeleting"
|
||||||
|
@click="clearSelection"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<XIcon class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{ t('fileManage.clearSelection') }}
|
||||||
|
</BaseButton>
|
||||||
|
<BaseButton
|
||||||
|
variant="danger"
|
||||||
|
size="sm"
|
||||||
|
:disabled="!hasSelectedFiles"
|
||||||
|
:loading="isBatchDeleting"
|
||||||
|
@click="deleteSelectedFiles"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<TrashIcon class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{ t('fileManage.batchDelete') }}
|
||||||
|
</BaseButton>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<DataTable :title="t('fileManage.allFiles')" :headers="fileTableHeaders">
|
<DataTable :title="t('fileManage.allFiles')" :headers="fileTableHeaders">
|
||||||
<template #body>
|
<template #body>
|
||||||
<tr v-if="isLoading">
|
<tr v-if="isLoading">
|
||||||
@@ -197,8 +248,25 @@
|
|||||||
v-for="file in tableData"
|
v-for="file in tableData"
|
||||||
:key="file.id"
|
:key="file.id"
|
||||||
class="transition-colors duration-200"
|
class="transition-colors duration-200"
|
||||||
:class="[isDarkMode ? 'hover:bg-gray-700/70' : 'hover:bg-gray-50']"
|
:class="[
|
||||||
|
selectedFileIds.has(file.id)
|
||||||
|
? isDarkMode
|
||||||
|
? 'bg-indigo-950/30'
|
||||||
|
: 'bg-indigo-50/70'
|
||||||
|
: '',
|
||||||
|
isDarkMode ? 'hover:bg-gray-700/70' : 'hover:bg-gray-50'
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<input
|
||||||
|
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"
|
||||||
|
:aria-label="t('fileManage.selectFile', { name: file.displayName })"
|
||||||
|
@change="toggleFileSelection(file.id)"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
<span class="font-medium select-all" :class="[primaryTextClass]">
|
<span class="font-medium select-all" :class="[primaryTextClass]">
|
||||||
{{ file.code }}
|
{{ file.code }}
|
||||||
@@ -508,6 +576,7 @@ const { t } = useI18n()
|
|||||||
const isDarkMode = useInjectedDarkMode()
|
const isDarkMode = useInjectedDarkMode()
|
||||||
|
|
||||||
const fileTableHeaders = computed(() => [
|
const fileTableHeaders = computed(() => [
|
||||||
|
t('fileManage.headers.select'),
|
||||||
t('fileManage.headers.code'),
|
t('fileManage.headers.code'),
|
||||||
t('fileManage.headers.name'),
|
t('fileManage.headers.name'),
|
||||||
t('fileManage.headers.type'),
|
t('fileManage.headers.type'),
|
||||||
@@ -523,12 +592,18 @@ const {
|
|||||||
hasActiveFilters,
|
hasActiveFilters,
|
||||||
hasLoadError,
|
hasLoadError,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isAllCurrentPageSelected,
|
||||||
|
isBatchDeleting,
|
||||||
|
isCurrentPagePartiallySelected,
|
||||||
isPreviewLoading,
|
isPreviewLoading,
|
||||||
isSaving,
|
isSaving,
|
||||||
downloadingFileId,
|
downloadingFileId,
|
||||||
|
hasSelectedFiles,
|
||||||
params,
|
params,
|
||||||
previewFile,
|
previewFile,
|
||||||
previewMetaText,
|
previewMetaText,
|
||||||
|
selectedCount,
|
||||||
|
selectedFileIds,
|
||||||
storageUsedText,
|
storageUsedText,
|
||||||
summary,
|
summary,
|
||||||
showEditModal,
|
showEditModal,
|
||||||
@@ -538,7 +613,9 @@ const {
|
|||||||
closeEditModal,
|
closeEditModal,
|
||||||
closeTextPreview,
|
closeTextPreview,
|
||||||
copyText,
|
copyText,
|
||||||
|
clearSelection,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
|
deleteSelectedFiles,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
exportPreviewText,
|
exportPreviewText,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
@@ -550,7 +627,9 @@ const {
|
|||||||
refreshFiles,
|
refreshFiles,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
setStatusFilter,
|
setStatusFilter,
|
||||||
setTypeFilter
|
setTypeFilter,
|
||||||
|
toggleCurrentPageSelection,
|
||||||
|
toggleFileSelection
|
||||||
} = useAdminFiles()
|
} = useAdminFiles()
|
||||||
|
|
||||||
const primaryTextClass = computed(() => (isDarkMode.value ? 'text-white' : 'text-gray-900'))
|
const primaryTextClass = computed(() => (isDarkMode.value ? 'text-white' : 'text-gray-900'))
|
||||||
|
|||||||
Reference in New Issue
Block a user