更新架构

This commit is contained in:
2026-01-27 18:01:54 +08:00
parent 2420ce7660
commit da378a01be
33 changed files with 1490 additions and 239 deletions
+472
View File
@@ -0,0 +1,472 @@
<template>
<div class="container-box">
<div class="header-bar">
<h2>模块管理</h2>
<div class="header-actions">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
添加模块
</el-button>
<el-button @click="refresh">
<el-icon><Refresh /></el-icon>
刷新
</el-button>
</div>
</div>
<el-divider></el-divider>
<el-table
:data="modules"
style="width: 100%"
v-loading="loading"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column
prop="name"
label="模块名称"
min-width="150"
align="center"
>
<template #default="{ row }">
<div class="module-name">
<span class="module-icon" v-html="row.icon"></span>
<span>{{ row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column
prop="code"
label="模块编码"
min-width="120"
align="center"
/>
<el-table-column
prop="path"
label="路由路径"
min-width="150"
align="center"
>
<template #default="{ row }">
<el-tag size="small" type="info">{{ row.path || "-" }}</el-tag>
</template>
</el-table-column>
<el-table-column
prop="description"
label="描述"
min-width="200"
align="center"
show-overflow-tooltip
/>
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="is_show" label="显示" width="80" align="center">
<template #default="{ row }">
<el-switch
v-model="row.is_show"
:active-value="1"
:inactive-value="0"
@change="handleShowChange(row)"
/>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? "启用" : "禁用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="table-footer" v-if="selectedModules.length > 0">
<span>已选择 {{ selectedModules.length }} </span>
<el-button type="danger" size="small" @click="handleBatchDelete"
>批量删除</el-button
>
</div>
<el-divider></el-divider>
<div class="tips-section">
<el-alert title="模块管理说明" type="info" :closable="false" show-icon>
<template #default>
<p>1. 模块用于管理系统功能单元每个模块包含独立的路由图标和描述</p>
<p>2. 模块编码用于程序识别请确保唯一性</p>
<p>3. 禁用状态的模块将不会在菜单中显示</p>
</template>
</el-alert>
</div>
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '添加模块' : '编辑模块'"
width="600px"
:close-on-click-modal="false"
>
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="模块名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入模块名称" />
</el-form-item>
<el-form-item label="模块编码" prop="code">
<el-input
v-model="formData.code"
placeholder="请输入模块编码(英文)"
:disabled="dialogType === 'edit'"
/>
</el-form-item>
<el-form-item label="路由路径" prop="path">
<el-input
v-model="formData.path"
placeholder="请输入路由路径,如 /system/modules"
/>
</el-form-item>
<el-form-item label="图标" prop="icon">
<el-input
v-model="formData.icon"
placeholder="请输入图标类名,如 Grid"
>
<template #append>
<el-popover placement="bottom-end" :width="400" trigger="click">
<template #reference>
<el-button link>选择图标</el-button>
</template>
<div class="icon-grid">
<div
v-for="icon in iconList"
:key="icon"
class="icon-item"
:class="{ active: formData.icon === icon }"
@click="formData.icon = icon"
>
<el-icon :size="20"><component :is="icon" /></el-icon>
</div>
</div>
</el-popover>
</template>
</el-input>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input
v-model="formData.description"
type="textarea"
:rows="3"
placeholder="请输入模块描述"
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="formData.sort"
:min="0"
:max="999"
controls-position="right"
/>
</el-form-item>
<el-form-item label="是否显示" prop="is_show">
<el-switch
v-model="formData.is_show"
:active-value="1"
:inactive-value="0"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading"
>确定</el-button
>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, shallowRef } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
getModulesList,
getModuleDetail,
addModule,
editModule,
deleteModule,
batchDeleteModules,
changeModuleStatus,
} from "@/api/modules";
const loading = ref(false);
const modules = ref([]);
const selectedModules = ref([]);
const dialogVisible = ref(false);
const dialogType = ref("add");
const formRef = ref(null);
const submitLoading = ref(false);
const formData = ref({
name: "",
code: "",
path: "",
icon: "",
description: "",
sort: 0,
is_show: 1,
status: 1,
});
const rules = {
name: [{ required: true, message: "请输入模块名称", trigger: "blur" }],
code: [{ required: true, message: "请输入模块编码", trigger: "blur" }],
};
function getIconComponent(iconName) {
return iconComponents.value[iconName] || Grid;
}
async function fetchModules() {
loading.value = true;
try {
const res = await getModulesList();
if (res.code === 200 && res.data) {
modules.value = res.data.list || [];
}
} catch (error) {
console.error("获取模块列表失败:", error);
ElMessage.error("获取模块列表失败");
} finally {
loading.value = false;
}
}
function refresh() {
fetchModules();
}
function handleSelectionChange(selection) {
selectedModules.value = selection;
}
function handleAdd() {
dialogType.value = "add";
formData.value = {
name: "",
code: "",
path: "",
icon: "",
description: "",
sort: 0,
is_show: 1,
status: 1,
};
dialogVisible.value = true;
}
async function handleEdit(row) {
dialogType.value = "edit";
try {
const res = await getModuleDetail(row.id);
if (res.code === 200 && res.data) {
formData.value = { ...res.data };
dialogVisible.value = true;
} else {
ElMessage.error(res.msg || "获取模块详情失败");
}
} catch (error) {
console.error("获取模块详情失败:", error);
ElMessage.error("获取模块详情失败");
}
}
async function handleSubmit() {
try {
await formRef.value.validate();
submitLoading.value = true;
if (dialogType.value === "add") {
const res = await addModule(formData.value);
if (res.code === 200) {
ElMessage.success("添加成功");
dialogVisible.value = false;
fetchModules();
} else {
ElMessage.error(res.msg || "添加失败");
}
} else {
const res = await editModule(formData.value.id, formData.value);
if (res.code === 200) {
ElMessage.success("编辑成功");
dialogVisible.value = false;
fetchModules();
} else {
ElMessage.error(res.msg || "编辑失败");
}
}
} catch (error) {
console.error("提交失败:", error);
} finally {
submitLoading.value = false;
}
}
async function handleDelete(row) {
try {
await ElMessageBox.confirm("确定要删除该模块吗?", "提示", {
type: "warning",
});
const res = await deleteModule(row.id);
if (res.code === 200) {
ElMessage.success("删除成功");
fetchModules();
} else {
ElMessage.error(res.msg || "删除失败");
}
} catch (error) {
if (error !== "cancel") {
console.error("删除失败:", error);
ElMessage.error("删除失败");
}
}
}
async function handleBatchDelete() {
try {
await ElMessageBox.confirm(
`确定要删除选中的 ${selectedModules.value.length} 个模块吗?`,
"提示",
{
type: "warning",
},
);
const ids = selectedModules.value.map((item) => item.id);
const res = await batchDeleteModules(ids);
if (res.code === 200) {
ElMessage.success("批量删除成功");
fetchModules();
} else {
ElMessage.error(res.msg || "批量删除失败");
}
} catch (error) {
if (error !== "cancel") {
console.error("批量删除失败:", error);
ElMessage.error("批量删除失败");
}
}
}
async function handleShowChange(row) {
try {
const res = await changeModuleStatus(row.id, row.is_show);
if (res.code !== 200) {
ElMessage.error(res.msg || "状态修改失败");
row.is_show = row.is_show ? 0 : 1;
}
} catch (error) {
console.error("状态修改失败:", error);
row.is_show = row.is_show ? 0 : 1;
}
}
onMounted(() => {
fetchModules();
});
</script>
<style scoped lang="scss">
.container-box {
padding: 20px;
background: var(--el-bg-color);
border-radius: 8px;
}
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.header-actions {
display: flex;
gap: 12px;
}
.module-name {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
.module-icon {
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #667eea;
}
}
.table-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
}
.tips-section {
margin-top: 20px;
p {
margin: 4px 0;
font-size: 13px;
color: var(--el-text-color-secondary);
}
}
.icon-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 8px;
max-height: 200px;
overflow-y: auto;
.icon-item {
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
&:hover,
&.active {
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
}
}
</style>