187 lines
6.1 KiB
Vue
187 lines
6.1 KiB
Vue
<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>
|