✨ 整合文件选择工具栏
This commit is contained in:
@@ -2,10 +2,16 @@
|
|||||||
<div
|
<div
|
||||||
class="data-table-2026 theme-2026-card theme-2026-card-hover overflow-hidden rounded-[30px] shadow-md transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
|
class="data-table-2026 theme-2026-card theme-2026-card-hover overflow-hidden rounded-[30px] shadow-md transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
|
||||||
>
|
>
|
||||||
<div class="px-6 py-4 border-b" :class="[isDarkMode ? 'border-gray-700/80' : 'border-[#d9e4e6]']">
|
<div
|
||||||
|
class="border-b px-6 py-4"
|
||||||
|
:class="[isDarkMode ? 'border-gray-700/80' : 'border-[#d9e4e6]']"
|
||||||
|
>
|
||||||
<h3 class="text-lg font-medium" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
<h3 class="text-lg font-medium" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h3>
|
</h3>
|
||||||
|
<div v-if="slots.toolbar" class="mt-4">
|
||||||
|
<slot name="toolbar"></slot>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table
|
<table
|
||||||
@@ -14,9 +20,12 @@
|
|||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th v-for="header in headers" :key="header"
|
<th
|
||||||
|
v-for="header in headers"
|
||||||
|
:key="header"
|
||||||
class="px-6 py-3.5 text-left text-xs font-medium uppercase tracking-wider"
|
class="px-6 py-3.5 text-left text-xs font-medium uppercase tracking-wider"
|
||||||
:class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']">
|
:class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']"
|
||||||
|
>
|
||||||
{{ header }}
|
{{ header }}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -34,7 +43,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { inject } from 'vue'
|
import { inject, useSlots } from 'vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string
|
title: string
|
||||||
@@ -43,5 +52,6 @@ interface Props {
|
|||||||
|
|
||||||
defineProps<Props>()
|
defineProps<Props>()
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
const isDarkMode = inject('isDarkMode')
|
const isDarkMode = inject('isDarkMode')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ const isLegacyEndpointUnavailable = (error: unknown) => {
|
|||||||
|
|
||||||
const legacyForeverExpiresAt = '2099-12-31T23:59'
|
const legacyForeverExpiresAt = '2099-12-31T23:59'
|
||||||
const detailPolicyDownloadLimit = 5
|
const detailPolicyDownloadLimit = 5
|
||||||
|
const hourInMilliseconds = 60 * 60 * 1000
|
||||||
const dayInMilliseconds = 24 * 60 * 60 * 1000
|
const dayInMilliseconds = 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
type DetailPolicyActionOption = {
|
type DetailPolicyActionOption = {
|
||||||
@@ -99,7 +100,9 @@ type HealthFilterOption = {
|
|||||||
const emptyBatchEditForm = (): AdminBatchEditForm => ({
|
const emptyBatchEditForm = (): AdminBatchEditForm => ({
|
||||||
mode: 'expiresAt',
|
mode: 'expiresAt',
|
||||||
expired_at: '',
|
expired_at: '',
|
||||||
expired_count: null
|
expired_count: null,
|
||||||
|
relative_expire_value: null,
|
||||||
|
relative_expire_unit: 'hours'
|
||||||
})
|
})
|
||||||
|
|
||||||
const metadataTagSeparatorPattern = /[,,\n]+/
|
const metadataTagSeparatorPattern = /[,,\n]+/
|
||||||
@@ -269,6 +272,12 @@ export function useAdminFiles() {
|
|||||||
)
|
)
|
||||||
const selectedCount = computed(() => selectedFileIds.value.size)
|
const selectedCount = computed(() => selectedFileIds.value.size)
|
||||||
const hasSelectedFiles = computed(() => selectedCount.value > 0)
|
const hasSelectedFiles = computed(() => selectedCount.value > 0)
|
||||||
|
const selectedFilesOnCurrentPage = computed(() =>
|
||||||
|
tableData.value.filter((file) => selectedFileIds.value.has(file.id))
|
||||||
|
)
|
||||||
|
const selectedHasPermanentFiles = computed(() =>
|
||||||
|
selectedFilesOnCurrentPage.value.some((file) => file.isPermanentFile)
|
||||||
|
)
|
||||||
const isAllCurrentPageSelected = computed(
|
const isAllCurrentPageSelected = computed(
|
||||||
() => tableData.value.length > 0 && currentPageSelectedCount.value === tableData.value.length
|
() => tableData.value.length > 0 && currentPageSelectedCount.value === tableData.value.length
|
||||||
)
|
)
|
||||||
@@ -429,7 +438,7 @@ export function useAdminFiles() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const batchPolicyActionOptions = computed<BatchPolicyActionOption[]>(
|
const batchPolicyActionOptions = computed<BatchPolicyActionOption[]>(
|
||||||
() => createPolicyActionOptions(false)
|
() => createPolicyActionOptions(selectedHasPermanentFiles.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
const inferIsText = (file: FileListItem) => {
|
const inferIsText = (file: FileListItem) => {
|
||||||
@@ -571,6 +580,7 @@ export function useAdminFiles() {
|
|||||||
displayHealthAction: t(`fileManage.insightActions.${statusInsightNextAction}`),
|
displayHealthAction: t(`fileManage.insightActions.${statusInsightNextAction}`),
|
||||||
isTextFile,
|
isTextFile,
|
||||||
isExpiredFile,
|
isExpiredFile,
|
||||||
|
isPermanentFile,
|
||||||
isChunkedFile,
|
isChunkedFile,
|
||||||
remainingDownloadsValue,
|
remainingDownloadsValue,
|
||||||
canPreviewText: isTextFile,
|
canPreviewText: isTextFile,
|
||||||
@@ -823,6 +833,23 @@ export function useAdminFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (form.mode === 'relativeExpire') {
|
||||||
|
if (
|
||||||
|
form.relative_expire_value === null ||
|
||||||
|
!Number.isFinite(form.relative_expire_value) ||
|
||||||
|
form.relative_expire_value <= 0
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const duration =
|
||||||
|
form.relative_expire_value *
|
||||||
|
(form.relative_expire_unit === 'days' ? dayInMilliseconds : hourInMilliseconds)
|
||||||
|
return {
|
||||||
|
ids,
|
||||||
|
expired_at: formatLocalDateTime(new Date(Date.now() + duration))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ids,
|
ids,
|
||||||
clearExpiredAt: true,
|
clearExpiredAt: true,
|
||||||
@@ -1425,10 +1452,11 @@ export function useAdminFiles() {
|
|||||||
|
|
||||||
const applySelectedPolicyAction = async (action: AdminFilePolicyAction) => {
|
const applySelectedPolicyAction = async (action: AdminFilePolicyAction) => {
|
||||||
if (!hasSelectedFiles.value || isBatchActionRunning.value) return
|
if (!hasSelectedFiles.value || isBatchActionRunning.value) return
|
||||||
|
const selectedAction = batchPolicyActionOptions.value.find((option) => option.action === action)
|
||||||
|
if (selectedAction?.disabled) return
|
||||||
|
|
||||||
const ids = Array.from(selectedFileIds.value)
|
const ids = Array.from(selectedFileIds.value)
|
||||||
const actionLabel =
|
const actionLabel = selectedAction?.label || action
|
||||||
batchPolicyActionOptions.value.find((option) => option.action === action)?.label || action
|
|
||||||
if (
|
if (
|
||||||
!window.confirm(
|
!window.confirm(
|
||||||
t('fileManage.batchPolicyActionConfirm', {
|
t('fileManage.batchPolicyActionConfirm', {
|
||||||
@@ -1690,6 +1718,7 @@ export function useAdminFiles() {
|
|||||||
selectedFileDetail,
|
selectedFileDetail,
|
||||||
selectedCount,
|
selectedCount,
|
||||||
selectedFileIds,
|
selectedFileIds,
|
||||||
|
selectedHasPermanentFiles,
|
||||||
selectedViewPresetId,
|
selectedViewPresetId,
|
||||||
storageUsedText,
|
storageUsedText,
|
||||||
summary,
|
summary,
|
||||||
|
|||||||
@@ -485,6 +485,12 @@ export default {
|
|||||||
batchEditSelected: 'Update the selected {count} files',
|
batchEditSelected: 'Update the selected {count} files',
|
||||||
batchEditMode: 'Update Mode',
|
batchEditMode: 'Update Mode',
|
||||||
batchEditExpiresAt: 'Set Expiration Time',
|
batchEditExpiresAt: 'Set Expiration Time',
|
||||||
|
batchEditRelativeExpire: 'Expire After Duration',
|
||||||
|
batchEditRelativeExpireValue: 'Expiration Duration',
|
||||||
|
batchEditRelativeExpirePlaceholder: 'Enter amount',
|
||||||
|
batchEditRelativeExpireUnit: 'Unit',
|
||||||
|
batchEditRelativeExpireHours: 'Hours',
|
||||||
|
batchEditRelativeExpireDays: 'Days',
|
||||||
batchEditDownloadLimit: 'Set Retrieval Limit',
|
batchEditDownloadLimit: 'Set Retrieval Limit',
|
||||||
batchEditForever: 'Make Permanent',
|
batchEditForever: 'Make Permanent',
|
||||||
batchEditForeverHint: 'Clear the expiration time and make retrievals unlimited.',
|
batchEditForeverHint: 'Clear the expiration time and make retrievals unlimited.',
|
||||||
|
|||||||
@@ -483,6 +483,12 @@ export default {
|
|||||||
batchEditSelected: '将更新选中的 {count} 个文件',
|
batchEditSelected: '将更新选中的 {count} 个文件',
|
||||||
batchEditMode: '更新方式',
|
batchEditMode: '更新方式',
|
||||||
batchEditExpiresAt: '设置过期时间',
|
batchEditExpiresAt: '设置过期时间',
|
||||||
|
batchEditRelativeExpire: '设为一段时间后消失',
|
||||||
|
batchEditRelativeExpireValue: '消失时间',
|
||||||
|
batchEditRelativeExpirePlaceholder: '输入数量',
|
||||||
|
batchEditRelativeExpireUnit: '单位',
|
||||||
|
batchEditRelativeExpireHours: '小时',
|
||||||
|
batchEditRelativeExpireDays: '天',
|
||||||
batchEditDownloadLimit: '设置取件次数',
|
batchEditDownloadLimit: '设置取件次数',
|
||||||
batchEditForever: '设为永久有效',
|
batchEditForever: '设为永久有效',
|
||||||
batchEditForeverHint: '清空过期时间,并将取件次数设为不限。',
|
batchEditForeverHint: '清空过期时间,并将取件次数设为不限。',
|
||||||
|
|||||||
+6
-1
@@ -47,6 +47,7 @@ export interface AdminFileViewItem extends FileListItem {
|
|||||||
displayHealthAction: string
|
displayHealthAction: string
|
||||||
isTextFile: boolean
|
isTextFile: boolean
|
||||||
isExpiredFile: boolean
|
isExpiredFile: boolean
|
||||||
|
isPermanentFile: boolean
|
||||||
isChunkedFile: boolean
|
isChunkedFile: boolean
|
||||||
remainingDownloadsValue: number | null
|
remainingDownloadsValue: number | null
|
||||||
canPreviewText: boolean
|
canPreviewText: boolean
|
||||||
@@ -393,12 +394,16 @@ export interface AdminBatchPolicyActionResponse extends AdminBatchUpdateFilesRes
|
|||||||
action?: AdminFilePolicyAction | string
|
action?: AdminFilePolicyAction | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminBatchEditMode = 'expiresAt' | 'downloadLimit' | 'forever'
|
export type AdminBatchExpireUnit = 'hours' | 'days'
|
||||||
|
|
||||||
|
export type AdminBatchEditMode = 'expiresAt' | 'relativeExpire' | 'downloadLimit' | 'forever'
|
||||||
|
|
||||||
export interface AdminBatchEditForm {
|
export interface AdminBatchEditForm {
|
||||||
mode: AdminBatchEditMode
|
mode: AdminBatchEditMode
|
||||||
expired_at: string
|
expired_at: string
|
||||||
expired_count: number | null
|
expired_count: number | null
|
||||||
|
relative_expire_value: number | null
|
||||||
|
relative_expire_unit: AdminBatchExpireUnit
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileUploadResponse {
|
export interface FileUploadResponse {
|
||||||
|
|||||||
@@ -261,85 +261,86 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section
|
|
||||||
v-if="tableData.length > 0"
|
|
||||||
class="theme-2026-card theme-2026-card-hover mb-4 flex flex-col gap-3 rounded-[30px] border px-4 py-3 shadow-md transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg sm:flex-row sm:items-center sm:justify-between"
|
|
||||||
:class="[panelClass, cardHoverClass]"
|
|
||||||
>
|
|
||||||
<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="isBatchActionRunning"
|
|
||||||
: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="isBatchActionRunning"
|
|
||||||
@click="clearSelection"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<XIcon class="mr-2 h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
{{ t('fileManage.clearSelection') }}
|
|
||||||
</BaseButton>
|
|
||||||
<BaseButton
|
|
||||||
v-for="option in batchPolicyActionOptions"
|
|
||||||
:key="option.action"
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
:title="option.description"
|
|
||||||
:disabled="!hasSelectedFiles || isBatchDeleting || isBatchUpdating"
|
|
||||||
:loading="isBatchPolicyActionRunning"
|
|
||||||
@click="applySelectedPolicyAction(option.action)"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<component :is="getDetailPolicyActionIcon(option.action)" class="mr-2 h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
{{ option.label }}
|
|
||||||
</BaseButton>
|
|
||||||
<BaseButton
|
|
||||||
variant="secondary"
|
|
||||||
size="sm"
|
|
||||||
:disabled="!hasSelectedFiles || isBatchDeleting || isBatchPolicyActionRunning"
|
|
||||||
: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 || isBatchUpdating || isBatchPolicyActionRunning"
|
|
||||||
: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 #toolbar>
|
||||||
|
<div
|
||||||
|
v-if="tableData.length > 0"
|
||||||
|
class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
|
||||||
|
>
|
||||||
|
<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="isBatchActionRunning"
|
||||||
|
: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="isBatchActionRunning"
|
||||||
|
@click="clearSelection"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<XIcon class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{ t('fileManage.clearSelection') }}
|
||||||
|
</BaseButton>
|
||||||
|
<BaseButton
|
||||||
|
v-for="option in batchPolicyActionOptions"
|
||||||
|
:key="option.action"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
:title="option.description"
|
||||||
|
:disabled="option.disabled || !hasSelectedFiles || isBatchDeleting || isBatchUpdating"
|
||||||
|
:loading="isBatchPolicyActionRunning"
|
||||||
|
@click="applySelectedPolicyAction(option.action)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<component :is="getDetailPolicyActionIcon(option.action)" class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{ option.label }}
|
||||||
|
</BaseButton>
|
||||||
|
<BaseButton
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
:disabled="!hasSelectedFiles || isBatchDeleting || isBatchPolicyActionRunning"
|
||||||
|
: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 || isBatchUpdating || isBatchPolicyActionRunning"
|
||||||
|
:loading="isBatchDeleting"
|
||||||
|
@click="deleteSelectedFiles"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<TrashIcon class="mr-2 h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
{{ t('fileManage.batchDelete') }}
|
||||||
|
</BaseButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
<tr v-if="isLoading">
|
<tr v-if="isLoading">
|
||||||
<td :colspan="fileTableHeaders.length" class="px-6 py-12 text-center">
|
<td :colspan="fileTableHeaders.length" class="px-6 py-12 text-center">
|
||||||
@@ -1218,12 +1219,12 @@
|
|||||||
<p class="text-sm font-medium" :class="[primaryTextClass]">
|
<p class="text-sm font-medium" :class="[primaryTextClass]">
|
||||||
{{ t('fileManage.batchEditMode') }}
|
{{ t('fileManage.batchEditMode') }}
|
||||||
</p>
|
</p>
|
||||||
<div class="grid gap-2 sm:grid-cols-3">
|
<div class="grid gap-2 sm:grid-cols-2 xl:grid-cols-4">
|
||||||
<button
|
<button
|
||||||
v-for="option in batchEditModeOptions"
|
v-for="option in batchEditModeOptions"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
type="button"
|
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="flex min-h-12 items-center justify-center rounded-[30px] border px-3 py-2 text-sm font-medium transition-colors"
|
||||||
:class="getBatchEditModeClass(option.value)"
|
:class="getBatchEditModeClass(option.value)"
|
||||||
:disabled="isBatchUpdating"
|
:disabled="isBatchUpdating"
|
||||||
@click="batchEditForm.mode = option.value"
|
@click="batchEditForm.mode = option.value"
|
||||||
@@ -1240,6 +1241,42 @@
|
|||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
:label="t('fileManage.batchEditExpiresAt')"
|
:label="t('fileManage.batchEditExpiresAt')"
|
||||||
/>
|
/>
|
||||||
|
<div v-else-if="batchEditForm.mode === 'relativeExpire'" class="grid gap-3 sm:grid-cols-[1fr_10rem]">
|
||||||
|
<FileEditField
|
||||||
|
v-model="batchEditForm.relative_expire_value"
|
||||||
|
type="number"
|
||||||
|
:label="t('fileManage.batchEditRelativeExpireValue')"
|
||||||
|
:placeholder="t('fileManage.batchEditRelativeExpirePlaceholder')"
|
||||||
|
/>
|
||||||
|
<div class="space-y-2 group">
|
||||||
|
<label
|
||||||
|
class="flex items-center space-x-2 text-sm font-medium transition-colors duration-200"
|
||||||
|
:class="[
|
||||||
|
isDarkMode
|
||||||
|
? 'text-gray-300 group-focus-within:text-indigo-400'
|
||||||
|
: 'text-gray-700 group-focus-within:text-indigo-600'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span>{{ t('fileManage.batchEditRelativeExpireUnit') }}</span>
|
||||||
|
<div
|
||||||
|
class="h-px flex-1 transition-colors duration-200"
|
||||||
|
:class="[
|
||||||
|
isDarkMode
|
||||||
|
? 'bg-gray-700 group-focus-within:bg-indigo-500/50'
|
||||||
|
: 'bg-gray-200 group-focus-within:bg-indigo-500/30'
|
||||||
|
]"
|
||||||
|
></div>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
v-model="batchEditForm.relative_expire_unit"
|
||||||
|
class="block min-h-12 w-full rounded-[30px] border px-4 py-2.5 text-sm font-medium transition-all duration-200 focus:outline-none focus:ring-2"
|
||||||
|
:class="fieldClass"
|
||||||
|
>
|
||||||
|
<option value="hours">{{ t('fileManage.batchEditRelativeExpireHours') }}</option>
|
||||||
|
<option value="days">{{ t('fileManage.batchEditRelativeExpireDays') }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<FileEditField
|
<FileEditField
|
||||||
v-else-if="batchEditForm.mode === 'downloadLimit'"
|
v-else-if="batchEditForm.mode === 'downloadLimit'"
|
||||||
v-model="batchEditForm.expired_count"
|
v-model="batchEditForm.expired_count"
|
||||||
@@ -1377,6 +1414,7 @@ import {
|
|||||||
RefreshCwIcon,
|
RefreshCwIcon,
|
||||||
RotateCcwIcon,
|
RotateCcwIcon,
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
|
TimerResetIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
XIcon
|
XIcon
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
@@ -1815,6 +1853,11 @@ const batchEditModeOptions = computed<
|
|||||||
label: t('fileManage.batchEditExpiresAt'),
|
label: t('fileManage.batchEditExpiresAt'),
|
||||||
icon: ClockIcon
|
icon: ClockIcon
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'relativeExpire',
|
||||||
|
label: t('fileManage.batchEditRelativeExpire'),
|
||||||
|
icon: TimerResetIcon
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'downloadLimit',
|
value: 'downloadLimit',
|
||||||
label: t('fileManage.batchEditDownloadLimit'),
|
label: t('fileManage.batchEditDownloadLimit'),
|
||||||
|
|||||||
Reference in New Issue
Block a user