更新前后端代码

This commit is contained in:
2026-08-23 00:48:10 +08:00
parent 1b5468995f
commit 5c1313e4f6
74 changed files with 9995 additions and 306 deletions
+186
View File
@@ -0,0 +1,186 @@
<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>