feat: 新增永久删除文件接口和前端删除按钮
后端新增 POST api/files/delete 接口,同时删除物理文件、缩略图和数据库记录。 前端在 FileCard 和 FileListItem 中增加 hover 可见的删除按钮,点击后弹出确认提示。
This commit is contained in:
@@ -114,5 +114,7 @@ export const api = {
|
||||
request('files/played', { method: 'POST', body: { id } }),
|
||||
saveFileProgress: (id: number, position: number) =>
|
||||
request('files/progress', { method: 'POST', body: { id, position } }),
|
||||
deleteFile: (id: number) =>
|
||||
request('files/delete', { method: 'POST', body: { id } }),
|
||||
qrCode: () => request<{ url: string; qrCodeBase64: string }>('qrcode'),
|
||||
}
|
||||
|
||||
@@ -272,6 +272,24 @@ function exitSearch() {
|
||||
searchTotalPages.value = 0
|
||||
}
|
||||
|
||||
async function deleteFile(file: FileRecordDto) {
|
||||
if (!window.confirm(`确定要永久删除 "${file.fileName}" 吗?\n\n删除后无法恢复!`)) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await api.deleteFile(file.id)
|
||||
await browseDirectory()
|
||||
if (isSearching.value) {
|
||||
await doSearch(searchPage.value)
|
||||
}
|
||||
if (selectedFile.value?.id === file.id) {
|
||||
selectedFile.value = null
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
}
|
||||
}
|
||||
|
||||
function updatePlaybackPosition(id: number, position: number) {
|
||||
if (selectedFile.value?.id === id) {
|
||||
selectedFile.value.playbackPosition = position
|
||||
@@ -463,7 +481,7 @@ onBeforeUnmount(() => {
|
||||
<p v-else-if="searchResults.length === 0" class="empty-state">无匹配文件</p>
|
||||
<div v-else-if="viewMode === 'grid'" class="file-grid">
|
||||
<template v-for="file in searchResults" :key="file.id">
|
||||
<FileCard :file="file" show-created-time @select="selectSearchFile" />
|
||||
<FileCard :file="file" show-created-time @select="selectSearchFile" @delete="deleteFile" />
|
||||
<SelectedMediaPlayerHost
|
||||
v-if="selectedFile?.id === file.id"
|
||||
:ref="setMediaPlayer"
|
||||
@@ -488,7 +506,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div v-else class="file-list">
|
||||
<template v-for="file in searchResults" :key="file.id">
|
||||
<FileListItem :file="file" show-created-time @select="selectSearchFile" />
|
||||
<FileListItem :file="file" show-created-time @select="selectSearchFile" @delete="deleteFile" />
|
||||
<SelectedMediaPlayerHost
|
||||
v-if="selectedFile?.id === file.id"
|
||||
:ref="setMediaPlayer"
|
||||
@@ -531,7 +549,7 @@ onBeforeUnmount(() => {
|
||||
</p>
|
||||
<div v-else-if="viewMode === 'grid'" class="file-grid">
|
||||
<template v-for="file in recentFiles" :key="file.id">
|
||||
<FileCard :file="file" :selected="selectedFile?.id === file.id" @select="selectFile" />
|
||||
<FileCard :file="file" :selected="selectedFile?.id === file.id" @select="selectFile" @delete="deleteFile" />
|
||||
<SelectedMediaPlayerHost
|
||||
v-if="selectedFile?.id === file.id"
|
||||
:ref="setMediaPlayer"
|
||||
@@ -561,6 +579,7 @@ onBeforeUnmount(() => {
|
||||
:selected="selectedFile?.id === file.id"
|
||||
:show-last-played="activeTab === 'recent-played'"
|
||||
@select="selectFile"
|
||||
@delete="deleteFile"
|
||||
/>
|
||||
<SelectedMediaPlayerHost
|
||||
v-if="selectedFile?.id === file.id"
|
||||
@@ -630,6 +649,7 @@ onBeforeUnmount(() => {
|
||||
:selected="selectedFile?.id === file.id"
|
||||
show-created-time
|
||||
@select="selectFile"
|
||||
@delete="deleteFile"
|
||||
/>
|
||||
<SelectedMediaPlayerHost
|
||||
v-if="selectedFile?.id === file.id"
|
||||
@@ -661,6 +681,7 @@ onBeforeUnmount(() => {
|
||||
:selected="selectedFile?.id === file.id"
|
||||
show-created-time
|
||||
@select="selectFile"
|
||||
@delete="deleteFile"
|
||||
/>
|
||||
<SelectedMediaPlayerHost
|
||||
v-if="selectedFile?.id === file.id"
|
||||
|
||||
@@ -10,6 +10,7 @@ defineProps<{
|
||||
|
||||
defineEmits<{
|
||||
select: [file: FileRecordDto]
|
||||
delete: [file: FileRecordDto]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -20,6 +21,7 @@ defineEmits<{
|
||||
type="button"
|
||||
@click="$emit('select', file)"
|
||||
>
|
||||
<span class="delete-btn" @click.stop="$emit('delete', file)" title="永久删除">删除</span>
|
||||
<img
|
||||
v-if="file.thumbnailUrl"
|
||||
:src="api.thumbnailUrl(file.thumbnailUrl)"
|
||||
@@ -38,3 +40,26 @@ defineEmits<{
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
.file-card:hover .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
.delete-btn:hover {
|
||||
background: #e53e3e;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,6 +11,7 @@ defineProps<{
|
||||
|
||||
defineEmits<{
|
||||
select: [file: FileRecordDto]
|
||||
delete: [file: FileRecordDto]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -38,5 +39,29 @@ defineEmits<{
|
||||
</small>
|
||||
<small v-if="showCreatedTime && formatCreatedTime(file)">{{ formatCreatedTime(file) }}</small>
|
||||
</span>
|
||||
<span class="delete-btn" @click.stop="$emit('delete', file)" title="永久删除">删除</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.delete-btn {
|
||||
margin-left: auto;
|
||||
padding: 2px 10px;
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
.mobile-file:hover .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
.delete-btn:hover {
|
||||
background: #e53e3e;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user