backend/src/views/moduleshop/publish/index.vue

456 lines
12 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.

<template>
<div class="publish-container">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>发布管理</span>
<el-button type="primary" :icon="Plus" @click="handlePublish">
新增模块
</el-button>
</div>
</template>
<el-table
:data="publishList"
v-loading="loading"
stripe
border
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="title" label="模块标题" min-width="200" show-overflow-tooltip />
<el-table-column prop="version" label="版本号" width="120" />
<el-table-column prop="module_name" label="所属分类" width="150" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">
{{ getStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="author" label="作者" width="120" />
<el-table-column prop="publish_time" label="发布时间" width="180">
<template #default="{ row }">
{{ formatDate(row.publish_time) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
link
size="small"
:icon="View"
@click="handleView(row)"
>
查看
</el-button>
<el-button
type="danger"
link
size="small"
:icon="Delete"
@click="handleDelete(row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="publishList.length === 0 && !loading" class="empty-state">
<el-empty description="暂无发布记录" />
</div>
</el-card>
<el-dialog
v-model="publishDialogVisible"
title="新增模块"
width="700px"
:close-on-click-modal="false"
@close="handlePublishClose"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
>
<el-form-item label="模块标题" prop="title">
<el-input
v-model="formData.title"
placeholder="请输入模块标题"
clearable
/>
</el-form-item>
<el-form-item label="版本号" prop="version">
<el-input
v-model="formData.version"
placeholder="请输入版本号,如: 1.0.0"
clearable
/>
</el-form-item>
<el-form-item label="所属分类" prop="module_id">
<el-select
v-model="formData.module_id"
placeholder="请选择所属分类"
clearable
>
<el-option
v-for="module in moduleOptions"
:key="module.id"
:label="module.name"
:value="module.id"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :value="1">发布</el-radio>
<el-radio :value="0">草稿</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="上传包" prop="packageFile">
<el-upload
ref="uploadRef"
class="upload-demo"
drag
:auto-upload="false"
:limit="1"
:on-change="handleFileChange"
:before-upload="beforeUpload"
accept=".zip"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">
拖拽文件到此处或<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传 zip 格式的压缩包且不超过 100MB
</div>
</template>
</el-upload>
<div v-if="formData.packageFile" class="file-info">
<el-icon><Document /></el-icon>
<span>{{ formData.packageFile.name }}</span>
<el-button
type="danger"
link
size="small"
@click="handleRemoveFile"
>
删除
</el-button>
</div>
</el-form-item>
<el-form-item label="模块说明" prop="description">
<el-input
v-model="formData.description"
type="textarea"
:rows="4"
placeholder="请输入模块说明"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="publishDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
确定
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, View, Delete, UploadFilled, Document } from "@element-plus/icons-vue";
import type { FormInstance, FormRules, UploadFile } from "element-plus";
import { getModules, getModuleCategory } from "@/api/moduleCenter";
interface Publish {
id: number;
title: string;
version: string;
author: string;
module_name: string;
status: number;
publish_time: string;
}
interface ModuleOption {
id: number;
name: string;
}
const loading = ref(false);
const publishList = ref<Publish[]>([]);
const publishDialogVisible = ref(false);
const submitLoading = ref(false);
const formRef = ref<FormInstance>();
const uploadRef = ref();
const formData = reactive({
title: "",
version: "",
author: "",
module_id: null as number | null,
status: 1,
description: "",
packageFile: null as File | null
});
const moduleOptions = ref<ModuleOption[]>([]);
const formRules: FormRules = {
title: [
{ required: true, message: "请输入模块标题", trigger: "blur" }
],
version: [
{ required: true, message: "请输入版本号", trigger: "blur" },
{ pattern: /^\d+\.\d+\.\d+$/, message: "版本号格式不正确,如: 1.0.0", trigger: "blur" }
],
module_id: [
{ required: true, message: "请选择所属分类", trigger: "change" }
],
status: [
{ required: true, message: "请选择状态", trigger: "change" }
]
};
const getStatusText = (status: number) => {
const map: Record<number, string> = {
0: "草稿",
1: "已发布",
2: "已撤回"
};
return map[status] || "未知";
};
const getStatusType = (status: number) => {
const map: Record<number, any> = {
0: "info",
1: "success",
2: "warning"
};
return map[status] || "info";
};
const formatDate = (date: string) => {
if (!date) return "-";
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
const hours = String(d.getHours()).padStart(2, "0");
const minutes = String(d.getMinutes()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}`;
};
const handlePublish = () => {
publishDialogVisible.value = true;
fetchModuleOptions();
};
const handleView = (row: Publish) => {
ElMessage.info("查看模块详情功能开发中");
};
const handleDelete = (row: Publish) => {
ElMessageBox.confirm(`确定要删除模块"${row.title}"吗?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
try {
// TODO: 调用删除接口
// await deletePublish(row.id);
ElMessage.success("删除成功");
handleRefresh();
} catch (error) {
console.error("删除失败:", error);
ElMessage.error("删除失败");
}
}).catch(() => {});
};
const handleFileChange = (file: UploadFile) => {
formData.packageFile = file.raw;
};
const beforeUpload = (file: File) => {
const isZip = file.type === "application/zip" || file.name.endsWith(".zip");
const isLt100M = file.size / 1024 / 1024 < 100 * 1024;
if (!isZip) {
ElMessage.error("只能上传 zip 格式的文件!");
return false;
}
if (!isLt100M) {
ElMessage.error("文件大小不能超过 100MB!");
return false;
}
return true;
};
const handleRemoveFile = () => {
formData.packageFile = null;
if (uploadRef.value) {
uploadRef.value.clearFiles();
}
};
const handlePublishClose = () => {
formRef.value?.resetFields();
formData.packageFile = null;
publishDialogVisible.value = false;
};
const handleSubmit = async () => {
if (!formRef.value) return;
await formRef.value.validate(async (valid) => {
if (valid) {
if (!formData.packageFile) {
ElMessage.warning("请上传模块包");
return;
}
submitLoading.value = true;
try {
// TODO: 调用模块接口
const formDataObj = new FormData();
formDataObj.append("title", formData.title);
formDataObj.append("version", formData.version);
formDataObj.append("author", formData.author);
formDataObj.append("module_id", formData.module_id.toString());
formDataObj.append("status", formData.status.toString());
formDataObj.append("description", formData.description);
formDataObj.append("package", formData.packageFile);
ElMessage.success("发布成功");
handlePublishClose();
handleRefresh();
} catch (error) {
console.error("发布失败:", error);
ElMessage.error("发布失败");
} finally {
submitLoading.value = false;
}
}
});
};
const handleRefresh = () => {
fetchPublishList();
};
const fetchPublishList = async () => {
loading.value = true;
try {
const res = await getModules(0);
if (res.code === 200 && res.data) {
publishList.value = res.data.list.map((item: any) => ({
id: item.id,
title: item.title,
version: item.version || "-",
author: item.author || "-",
module_name: item.cid || "",
status: item.status,
publish_time: item.create_time || ""
}));
}
} catch (error) {
console.error("获取模块列表失败:", error);
ElMessage.error("获取模块列表失败");
} finally {
loading.value = false;
}
};
const fetchModuleOptions = async () => {
try {
const res = await getModuleCategory();
if (res.code === 200 && res.data) {
moduleOptions.value = res.data.list.map((item: any) => ({
id: item.id,
name: item.title
}));
}
} catch (error) {
console.error("获取分类列表失败:", error);
}
};
onMounted(() => {
fetchPublishList();
});
</script>
<style scoped lang="scss">
.publish-container {
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.upload-demo {
width: 100%;
:deep(.el-upload) {
width: 100%;
}
:deep(.el-upload-dragger) {
width: 100%;
height: 180px;
}
}
.file-info {
margin-top: 12px;
padding: 8px 12px;
background: var(--el-fill-color-light);
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
.el-icon {
font-size: 20px;
color: var(--el-color-primary);
}
span {
flex: 1;
font-size: 14px;
color: var(--el-text-color-regular);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.el-upload__tip {
font-size: 12px;
color: var(--el-text-color-placeholder);
margin-top: 8px;
}
.empty-state {
padding: 40px 0;
}
}
</style>