Files
filestoragesystem/backend/src/views/Projects.vue
T
2026-08-23 00:48:10 +08:00

187 lines
6.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { projectApi, type Project } from '../api'
import { formatSize, formatTime } from '../utils/format'
const router = useRouter()
const loading = ref(false)
const list = ref<Project[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const keyword = ref('')
const dialogVisible = ref(false)
const editing = ref<Project | null>(null)
const form = reactive({ name: '', description: '', storage_limit: 0 })
const submitting = ref(false)
async function load() {
loading.value = true
try {
const r = await projectApi.list({ page: page.value, page_size: pageSize.value, keyword: keyword.value || undefined })
list.value = r.data!.list || []
total.value = r.data!.total
} finally {
loading.value = false
}
}
function openCreate() {
editing.value = null
form.name = ''
form.description = ''
form.storage_limit = 0
dialogVisible.value = true
}
function openEdit(p: Project) {
editing.value = p
form.name = p.name
form.description = p.description
form.storage_limit = p.storage_limit / (1024 * 1024) // 转为MB编辑
dialogVisible.value = true
}
async function submit() {
if (!form.name.trim()) {
ElMessage.warning('请输入项目名称')
return
}
submitting.value = true
const limitBytes = form.storage_limit > 0 ? Math.round(form.storage_limit * 1024 * 1024) : 0
try {
if (editing.value) {
await projectApi.update(editing.value.id, {
name: form.name,
description: form.description,
storage_limit: limitBytes,
})
ElMessage.success('项目已更新')
} else {
await projectApi.create({
name: form.name,
description: form.description,
storage_limit: limitBytes || undefined,
})
ElMessage.success('项目已创建')
}
dialogVisible.value = false
load()
} catch {
/* 拦截器已提示 */
} finally {
submitting.value = false
}
}
async function remove(p: Project) {
try {
await ElMessageBox.confirm(
`确定删除项目「${p.name}」?项目下所有文件将被一并删除且不可恢复。`,
'删除项目',
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' },
)
} catch {
return
}
await projectApi.remove(p.id)
ElMessage.success('项目已删除')
load()
}
function goFiles(p: Project) {
router.push({ path: '/files', query: { project_id: p.id } })
}
onMounted(load)
</script>
<template>
<div class="page-card">
<div class="page-toolbar">
<el-input
v-model="keyword"
placeholder="搜索项目名称"
clearable
style="width: 240px"
@keyup.enter="page = 1; load()"
@clear="page = 1; load()"
>
<template #prefix><el-icon><Search /></el-icon></template>
</el-input>
<el-button type="primary" @click="openCreate">
<el-icon><Plus /></el-icon>新建项目
</el-button>
</div>
<el-table :data="list" v-loading="loading" empty-text="暂无项目,点击右上角新建">
<el-table-column prop="id" label="ID" width="70" />
<el-table-column label="项目名称" min-width="160">
<template #default="{ row }">
<el-link type="primary" @click="goFiles(row)">{{ row.name }}</el-link>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="180" show-overflow-tooltip />
<el-table-column label="存储用量" min-width="200">
<template #default="{ row }">
<el-progress
:percentage="row.storage_limit ? Math.min(100, Math.round(row.storage_used / row.storage_limit * 100)) : 0"
:stroke-width="10"
>
<span class="mono">{{ formatSize(row.storage_used) }} / {{ formatSize(row.storage_limit) }}</span>
</el-progress>
</template>
</el-table-column>
<el-table-column label="状态" width="90">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
{{ row.status === 1 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" width="160">
<template #default="{ row }">{{ formatTime(row.created_at) }}</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="goFiles(row)">文件</el-button>
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button link type="danger" @click="remove(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-if="total > pageSize"
style="margin-top: 16px; justify-content: flex-end"
layout="total, prev, pager, next"
:total="total"
:page-size="pageSize"
v-model:current-page="page"
@current-change="load"
/>
<el-dialog v-model="dialogVisible" :title="editing ? '编辑项目' : '新建项目'" width="460px">
<el-form label-width="90px">
<el-form-item label="项目名称" required>
<el-input v-model="form.name" placeholder="请输入项目名称" maxlength="100" />
</el-form-item>
<el-form-item label="项目描述">
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="选填" />
</el-form-item>
<el-form-item label="存储配额">
<el-input-number v-model="form.storage_limit" :min="0" :step="512" style="width: 180px" />
<span style="margin-left: 8px; color: #909399">MB(0 表示使用系统默认)</span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submit">确定</el-button>
</template>
</el-dialog>
</div>
</template>