回收站,软删除(三天自动清空),以及默认链接三十分钟失效
This commit is contained in:
@@ -61,6 +61,10 @@ function navigateTo(path: string) {
|
||||
<el-icon><svg viewBox="0 0 24 24" fill="currentColor"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg></el-icon>
|
||||
<template #title>设置</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/recycle-bin">
|
||||
<el-icon><svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg></el-icon>
|
||||
<template #title>回收站</template>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
@@ -89,6 +93,7 @@ function navigateTo(path: string) {
|
||||
<div style="padding: 12px 0; cursor: pointer; font-size: 16px" @click="navigateTo('/')">📊 仪表盘</div>
|
||||
<div style="padding: 12px 0; cursor: pointer; font-size: 16px" @click="navigateTo('/knowledge-bases')">📚 知识库</div>
|
||||
<div style="padding: 12px 0; cursor: pointer; font-size: 16px" @click="navigateTo('/settings')">⚙️ 设置</div>
|
||||
<div style="padding: 12px 0; cursor: pointer; font-size: 16px" @click="navigateTo('/recycle-bin')">🗑️ 回收站</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ const router = createRouter({
|
||||
name: 'KbDetail',
|
||||
component: () => import('@/views/KbDetail.vue'),
|
||||
},
|
||||
{
|
||||
path: 'recycle-bin',
|
||||
name: 'RecycleBin',
|
||||
component: () => import('@/views/RecycleBin.vue'),
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
name: 'Settings',
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import apiClient from '@/api/client'
|
||||
|
||||
const data = ref<{
|
||||
documents: any[]
|
||||
categories: any[]
|
||||
knowledge_bases: any[]
|
||||
retention_days: number
|
||||
}>({
|
||||
documents: [],
|
||||
categories: [],
|
||||
knowledge_bases: [],
|
||||
retention_days: 3,
|
||||
})
|
||||
const loading = ref(false)
|
||||
const isMobile = ref(window.innerWidth <= 768)
|
||||
|
||||
onMounted(() => {
|
||||
load()
|
||||
window.addEventListener('resize', () => {
|
||||
isMobile.value = window.innerWidth <= 768
|
||||
})
|
||||
})
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data: d } = await apiClient.get('/recycle-bin')
|
||||
data.value = d
|
||||
} catch { /* ignore */ }
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function restoreDoc(item: any) {
|
||||
try {
|
||||
await apiClient.post(`/recycle-bin/documents/${item.id}/restore`)
|
||||
ElMessage.success('文档已恢复。')
|
||||
load()
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function purgeDoc(item: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`彻底删除「${item.name}」?文件与记录将不可恢复。`, '彻底删除', { type: 'warning' })
|
||||
await apiClient.delete(`/recycle-bin/documents/${item.id}`)
|
||||
ElMessage.success('已彻底删除。')
|
||||
load()
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function restoreCategory(item: any) {
|
||||
try {
|
||||
await apiClient.post(`/recycle-bin/categories/${item.id}/restore`)
|
||||
ElMessage.success('目录及同批内容已恢复。')
|
||||
load()
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function purgeCategory(item: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`彻底删除目录「${item.name}」及其同批删除的全部内容?`, '彻底删除', { type: 'warning' })
|
||||
await apiClient.delete(`/recycle-bin/categories/${item.id}`)
|
||||
ElMessage.success('已彻底删除。')
|
||||
load()
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function restoreKb(item: any) {
|
||||
try {
|
||||
await apiClient.post(`/recycle-bin/knowledge-bases/${item.id}/restore`)
|
||||
ElMessage.success('知识库已恢复。')
|
||||
load()
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function purgeKb(item: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`彻底删除知识库「${item.name}」及其全部文档?不可恢复。`, '彻底删除', { type: 'warning' })
|
||||
await apiClient.delete(`/recycle-bin/knowledge-bases/${item.id}`)
|
||||
ElMessage.success('已彻底删除。')
|
||||
load()
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function statusText(item: any) {
|
||||
if (item.days_left <= 0) return '即将自动清理'
|
||||
return `${item.days_left} 天后自动清理`
|
||||
}
|
||||
|
||||
function formatSize(bytes: number) {
|
||||
if (bytes < 1024) return bytes + ' B'
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 8px">
|
||||
<h1 style="margin: 0; font-size: 22px">🗑️ 回收站</h1>
|
||||
<span style="color: #999; font-size: 13px">
|
||||
删除的内容保留 {{ data.retention_days }} 天,超期自动彻底删除
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 文档 -->
|
||||
<el-card shadow="hover" style="margin-bottom: 16px">
|
||||
<template #header><span style="font-weight: bold">📄 文档({{ data.documents.length }})</span></template>
|
||||
<el-empty v-if="!data.documents.length" description="暂无" :image-size="48" />
|
||||
<div v-for="item in data.documents" :key="item.id" class="rb-item">
|
||||
<div class="rb-info">
|
||||
<div class="rb-name">{{ item.name }}</div>
|
||||
<div class="rb-meta">
|
||||
所属:{{ item.kb_name || '未知知识库' }} · {{ formatSize(item.file_size) }} ·
|
||||
删除于 {{ item.deleted_at }} ·
|
||||
<span :style="{ color: item.days_left <= 1 ? '#f56c6c' : '#999' }">{{ statusText(item) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rb-actions">
|
||||
<el-button size="small" type="primary" @click="restoreDoc(item)">恢复</el-button>
|
||||
<el-button size="small" type="danger" @click="purgeDoc(item)">彻底删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 目录 -->
|
||||
<el-card shadow="hover" style="margin-bottom: 16px">
|
||||
<template #header><span style="font-weight: bold">📁 目录({{ data.categories.length }})</span></template>
|
||||
<el-empty v-if="!data.categories.length" description="暂无" :image-size="48" />
|
||||
<div v-for="item in data.categories" :key="item.id" class="rb-item">
|
||||
<div class="rb-info">
|
||||
<div class="rb-name">{{ item.name }}</div>
|
||||
<div class="rb-meta">
|
||||
所属:{{ item.kb_name || '未知知识库' }} · 删除于 {{ item.deleted_at }} ·
|
||||
<span :style="{ color: item.days_left <= 1 ? '#f56c6c' : '#999' }">{{ statusText(item) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rb-actions">
|
||||
<el-button size="small" type="primary" @click="restoreCategory(item)">恢复</el-button>
|
||||
<el-button size="small" type="danger" @click="purgeCategory(item)">彻底删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 知识库 -->
|
||||
<el-card shadow="hover">
|
||||
<template #header><span style="font-weight: bold">📚 知识库({{ data.knowledge_bases.length }})</span></template>
|
||||
<el-empty v-if="!data.knowledge_bases.length" description="暂无" :image-size="48" />
|
||||
<div v-for="item in data.knowledge_bases" :key="item.id" class="rb-item">
|
||||
<div class="rb-info">
|
||||
<div class="rb-name">{{ item.name }}</div>
|
||||
<div class="rb-meta">
|
||||
删除于 {{ item.deleted_at }} ·
|
||||
<span :style="{ color: item.days_left <= 1 ? '#f56c6c' : '#999' }">{{ statusText(item) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rb-actions">
|
||||
<el-button size="small" type="primary" @click="restoreKb(item)">恢复</el-button>
|
||||
<el-button size="small" type="danger" @click="purgeKb(item)">彻底删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.rb-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.rb-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.rb-name {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.rb-meta {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.rb-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user