添加系统管理、登录模块的接口、标准化开发流程

This commit is contained in:
super
2026-01-08 20:49:42 +08:00
parent fef12b01e2
commit 8fa07e4952
40 changed files with 3126 additions and 1701 deletions

View File

@@ -0,0 +1,166 @@
<template>
<div class="page-container">
<!-- 操作栏 -->
<a-card class="table-card" :bordered="false">
<template #title>
<a-button type="primary" @click="handleAdd()">
<PlusOutlined /> 新增菜单
</a-button>
</template>
<!-- 表格树形 -->
<a-table
:columns="columns"
:data-source="treeData"
:loading="loading"
:pagination="false"
row-key="id"
:default-expand-all-rows="true"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'icon'">
<span v-if="record.icon" class="menu-icon"><component :is="record.icon" /> {{ record.icon }}</span>
<span v-else>-</span>
</template>
<template v-else-if="column.key === 'type'">
<a-tag :color="getTypeColor(record.type)">{{ getTypeName(record.type) }}</a-tag>
</template>
<template v-else-if="column.key === 'status'">
<a-tag :color="record.status === 1 ? 'green' : 'red'">
{{ record.status === 1 ? '正常' : '禁用' }}
</a-tag>
</template>
<template v-else-if="column.key === 'hidden'">
<a-tag :color="record.hidden === 1 ? 'orange' : 'default'">
{{ record.hidden === 1 ? '隐藏' : '显示' }}
</a-tag>
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="handleAdd(record.id)">添加子菜单</a-button>
<a-button type="link" size="small" @click="handleEdit(record as MenuRecord)">编辑</a-button>
<a-popconfirm title="确定删除该菜单吗?" @confirm="handleDelete(record.id)">
<a-button type="link" size="small" danger>删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</a-card>
<!-- 新增/编辑弹窗 -->
<MenuFormModal
v-model:visible="modalVisible"
:record="currentRecord"
:menu-tree="treeData"
@success="loadData"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue'
import type { MenuRecord, MenuFormData } from '@/types/system/menu'
import { getMenuList, deleteMenu } from '@/api/system/menu'
import MenuFormModal from '@/components/system/menu/MenuFormModal.vue'
import { buildMenuTree } from '@/utils/route'
// Note: buildMenuTree expects SysMenu, MenuRecord extends SysMenu so it's fine.
// Using 'any' cast if strict checking fails due to optional properties.
const loading = ref(false)
const treeData = ref<MenuRecord[]>([])
// 表格列定义
const columns = [
{ title: '菜单名称', dataIndex: 'name', key: 'name', width: 200 },
{ title: '编码', dataIndex: 'code', key: 'code', width: 150 },
{ title: '图标', dataIndex: 'icon', key: 'icon', width: 120 },
{ title: '类型', dataIndex: 'type', key: 'type', width: 100 },
{ title: '路径', dataIndex: 'path', key: 'path' },
{ title: '排序', dataIndex: 'sort', key: 'sort', width: 80 },
{ title: '状态', dataIndex: 'status', key: 'status', width: 80 },
{ title: '显示', dataIndex: 'hidden', key: 'hidden', width: 80 },
{ title: '操作', key: 'action', width: 220, fixed: 'right' as const }
]
// 弹窗相关
const modalVisible = ref(false)
const currentRecord = ref<MenuFormData | undefined>(undefined)
function getTypeColor(type: string): string {
const colorMap: Record<string, string> = {
directory: 'blue',
menu: 'green',
button: 'orange'
}
return colorMap[type] || 'default'
}
function getTypeName(type: string): string {
const nameMap: Record<string, string> = {
directory: '目录',
menu: '菜单',
button: '按钮'
}
return nameMap[type] || type
}
// 加载数据
async function loadData() {
loading.value = true
try {
const res = await getMenuList()
// res.data.data is the array
const list = res.data.data || []
// Cast to any because buildMenuTree typings might be strict about SysMenu properties
treeData.value = buildMenuTree(list as any[]) as unknown as MenuRecord[]
} catch (error) {
console.error('加载数据失败:', error)
} finally {
loading.value = false
}
}
function handleAdd(parentId?: number) {
currentRecord.value = {
parentId: parentId || 0
} as MenuFormData
modalVisible.value = true
}
function handleEdit(record: MenuRecord) {
currentRecord.value = { ...record }
modalVisible.value = true
}
async function handleDelete(id: number) {
try {
await deleteMenu(id)
message.success('删除成功')
loadData()
} catch (error) {
console.error('删除失败:', error)
}
}
onMounted(() => {
loadData()
})
</script>
<style scoped>
.page-container {
padding: 0;
}
.table-card {
background: #fff;
}
.menu-icon {
font-size: 14px;
color: #666;
}
</style>