feat: 菜单管理功能增强 - 自动创建Vue组件文件
后端改进: - MenuController 添加自动创建 Vue 组件文件功能 - 创建菜单时自动生成对应的 .vue 文件模板 - 修复路径处理逻辑,确保子菜单使用相对路径 - 添加菜单名称唯一性检查,自动添加时间戳避免重复 - 修复 ViewsPath 配置路径 - 修复文件写入编码为 UTF-8 前端改进: - 添加创建目录/子菜单的帮助说明 - 子菜单自动生成组件路径(如果用户未填写) - 添加 autoCreateComponent 参数支持 - 优化菜单类型判断逻辑
This commit is contained in:
parent
dcda5fa528
commit
c53f658f91
@ -64,3 +64,56 @@ export function fetchGetMenuList() {
|
|||||||
url: '/api/v3/system/menus/simple'
|
url: '/api/v3/system/menus/simple'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建菜单
|
||||||
|
export function fetchCreateMenu(data: {
|
||||||
|
parentId?: number | null
|
||||||
|
name: string
|
||||||
|
path: string
|
||||||
|
component?: string
|
||||||
|
title: string
|
||||||
|
icon?: string
|
||||||
|
sort?: number
|
||||||
|
isHide?: boolean
|
||||||
|
keepAlive?: boolean
|
||||||
|
link?: string
|
||||||
|
isIframe?: boolean
|
||||||
|
roles?: string[]
|
||||||
|
autoCreateComponent?: boolean // 是否自动创建组件文件
|
||||||
|
}) {
|
||||||
|
return request.post({
|
||||||
|
url: '/api/menu',
|
||||||
|
params: data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新菜单
|
||||||
|
export function fetchUpdateMenu(id: number, data: {
|
||||||
|
parentId?: number | null
|
||||||
|
name?: string
|
||||||
|
path?: string
|
||||||
|
component?: string
|
||||||
|
title?: string
|
||||||
|
icon?: string
|
||||||
|
sort?: number
|
||||||
|
isHide?: boolean
|
||||||
|
keepAlive?: boolean
|
||||||
|
link?: string
|
||||||
|
isIframe?: boolean
|
||||||
|
roles?: string[]
|
||||||
|
}) {
|
||||||
|
return request.put({
|
||||||
|
url: `/api/menu/${id}`,
|
||||||
|
params: data,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除菜单
|
||||||
|
export function fetchDeleteMenu(id: number) {
|
||||||
|
return request.del({
|
||||||
|
url: `/api/menu/${id}`,
|
||||||
|
showSuccessMessage: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
>
|
>
|
||||||
<template #left>
|
<template #left>
|
||||||
<ElButton v-auth="'add'" @click="handleAddMenu" v-ripple> 添加菜单 </ElButton>
|
<ElButton @click="handleAddDirectory" v-ripple> 添加目录 </ElButton>
|
||||||
<ElButton @click="toggleExpand" v-ripple>
|
<ElButton @click="toggleExpand" v-ripple>
|
||||||
{{ isExpanded ? '收起' : '展开' }}
|
{{ isExpanded ? '收起' : '展开' }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
@ -43,6 +43,7 @@
|
|||||||
:type="dialogType"
|
:type="dialogType"
|
||||||
:editData="editData"
|
:editData="editData"
|
||||||
:lockType="lockMenuType"
|
:lockType="lockMenuType"
|
||||||
|
:isDirectory="currentParentId === null && !editData?.id"
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
/>
|
/>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
@ -55,7 +56,7 @@
|
|||||||
import { useTableColumns } from '@/hooks/core/useTableColumns'
|
import { useTableColumns } from '@/hooks/core/useTableColumns'
|
||||||
import type { AppRouteRecord } from '@/types/router'
|
import type { AppRouteRecord } from '@/types/router'
|
||||||
import MenuDialog from './modules/menu-dialog.vue'
|
import MenuDialog from './modules/menu-dialog.vue'
|
||||||
import { fetchGetMenuList } from '@/api/system-manage'
|
import { fetchGetMenuList, fetchCreateMenu, fetchUpdateMenu, fetchDeleteMenu } from '@/api/system-manage'
|
||||||
import { ElTag, ElMessageBox } from 'element-plus'
|
import { ElTag, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
defineOptions({ name: 'Menus' })
|
defineOptions({ name: 'Menus' })
|
||||||
@ -124,7 +125,8 @@
|
|||||||
row: AppRouteRecord
|
row: AppRouteRecord
|
||||||
): 'primary' | 'success' | 'warning' | 'info' | 'danger' => {
|
): 'primary' | 'success' | 'warning' | 'info' | 'danger' => {
|
||||||
if (row.meta?.isAuthButton) return 'danger'
|
if (row.meta?.isAuthButton) return 'danger'
|
||||||
if (row.children?.length) return 'info'
|
// 目录判断:有子菜单 或者 component 为 /index/index 且路径以 / 开头
|
||||||
|
if (row.children?.length || (row.component === '/index/index' && row.path?.startsWith('/'))) return 'info'
|
||||||
if (row.meta?.link && row.meta?.isIframe) return 'success'
|
if (row.meta?.link && row.meta?.isIframe) return 'success'
|
||||||
if (row.path) return 'primary'
|
if (row.path) return 'primary'
|
||||||
if (row.meta?.link) return 'warning'
|
if (row.meta?.link) return 'warning'
|
||||||
@ -138,7 +140,8 @@
|
|||||||
*/
|
*/
|
||||||
const getMenuTypeText = (row: AppRouteRecord): string => {
|
const getMenuTypeText = (row: AppRouteRecord): string => {
|
||||||
if (row.meta?.isAuthButton) return '按钮'
|
if (row.meta?.isAuthButton) return '按钮'
|
||||||
if (row.children?.length) return '目录'
|
// 目录判断:有子菜单 或者 component 为 /index/index 且没有父级
|
||||||
|
if (row.children?.length || (row.component === '/index/index' && row.path?.startsWith('/'))) return '目录'
|
||||||
if (row.meta?.link && row.meta?.isIframe) return '内嵌'
|
if (row.meta?.link && row.meta?.isIframe) return '内嵌'
|
||||||
if (row.path) return '菜单'
|
if (row.path) return '菜单'
|
||||||
if (row.meta?.link) return '外链'
|
if (row.meta?.link) return '外链'
|
||||||
@ -213,8 +216,8 @@
|
|||||||
return h('div', buttonStyle, [
|
return h('div', buttonStyle, [
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'add',
|
type: 'add',
|
||||||
onClick: () => handleAddAuth(),
|
onClick: () => handleAddChildMenu(row),
|
||||||
title: '新增权限'
|
title: '新增菜单'
|
||||||
}),
|
}),
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'edit',
|
type: 'edit',
|
||||||
@ -222,7 +225,7 @@
|
|||||||
}),
|
}),
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'delete',
|
type: 'delete',
|
||||||
onClick: () => handleDeleteMenu()
|
onClick: () => handleDeleteMenu(row)
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@ -351,12 +354,28 @@
|
|||||||
return convertAuthListToChildren(searchedData)
|
return convertAuthListToChildren(searchedData)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 当前正在创建子菜单的父级ID
|
||||||
|
const currentParentId = ref<number | null>(null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加菜单
|
* 添加目录(顶级菜单,parentId=null)
|
||||||
*/
|
*/
|
||||||
const handleAddMenu = (): void => {
|
const handleAddDirectory = (): void => {
|
||||||
dialogType.value = 'menu'
|
dialogType.value = 'menu'
|
||||||
editData.value = null
|
editData.value = null
|
||||||
|
currentParentId.value = null // 顶级目录,无父级
|
||||||
|
lockMenuType.value = true
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加子菜单(在目录下创建,parentId=父级id)
|
||||||
|
* @param parentRow 父级目录
|
||||||
|
*/
|
||||||
|
const handleAddChildMenu = (parentRow: AppRouteRecord): void => {
|
||||||
|
dialogType.value = 'menu'
|
||||||
|
editData.value = null // 新建模式,不传editData
|
||||||
|
currentParentId.value = parentRow.id || null // 保存父级ID
|
||||||
lockMenuType.value = true
|
lockMenuType.value = true
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
@ -406,6 +425,11 @@
|
|||||||
icon?: string
|
icon?: string
|
||||||
roles?: string[]
|
roles?: string[]
|
||||||
sort?: number
|
sort?: number
|
||||||
|
label?: string
|
||||||
|
isHide?: boolean
|
||||||
|
keepAlive?: boolean
|
||||||
|
link?: string
|
||||||
|
isIframe?: boolean
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,27 +437,99 @@
|
|||||||
* 提交表单数据
|
* 提交表单数据
|
||||||
* @param formData 表单数据
|
* @param formData 表单数据
|
||||||
*/
|
*/
|
||||||
const handleSubmit = (formData: MenuFormData): void => {
|
const handleSubmit = async (formData: MenuFormData): Promise<void> => {
|
||||||
console.log('提交数据:', formData)
|
try {
|
||||||
// TODO: 调用API保存数据
|
// 判断是否是编辑模式(有id)
|
||||||
getMenuList()
|
const isEdit = editData.value?.id
|
||||||
|
|
||||||
|
if (isEdit) {
|
||||||
|
// 更新菜单
|
||||||
|
await fetchUpdateMenu(editData.value.id, {
|
||||||
|
name: formData.label || formData.name,
|
||||||
|
path: formData.path,
|
||||||
|
component: formData.component,
|
||||||
|
title: formData.name,
|
||||||
|
icon: formData.icon,
|
||||||
|
sort: formData.sort,
|
||||||
|
isHide: formData.isHide,
|
||||||
|
keepAlive: formData.keepAlive,
|
||||||
|
link: formData.link,
|
||||||
|
isIframe: formData.isIframe,
|
||||||
|
roles: formData.roles
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 创建菜单/目录
|
||||||
|
// currentParentId.value=null 表示顶级目录,有值表示子菜单
|
||||||
|
const isDirectory = currentParentId.value === null
|
||||||
|
|
||||||
|
// 处理路径:子菜单路径不能以 / 开头,需要是相对路径
|
||||||
|
let menuPath = formData.path
|
||||||
|
if (!isDirectory && menuPath.startsWith('/')) {
|
||||||
|
// 子菜单:去掉开头的 /,只保留最后一段路径
|
||||||
|
const pathParts = menuPath.split('/').filter(Boolean)
|
||||||
|
menuPath = pathParts[pathParts.length - 1] || menuPath.replace(/^\//, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 目录路径必须以 / 开头
|
||||||
|
if (isDirectory && !menuPath.startsWith('/')) {
|
||||||
|
menuPath = '/' + menuPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// 子菜单必须有组件路径,如果没有填写则自动生成
|
||||||
|
let componentPath = formData.component
|
||||||
|
if (!isDirectory && !componentPath) {
|
||||||
|
// 获取父级目录的路径,生成组件路径
|
||||||
|
const parentMenu = tableData.value.find(m => m.id === currentParentId.value)
|
||||||
|
const parentPath = parentMenu?.path?.replace(/^\//, '') || 'pages'
|
||||||
|
componentPath = `/${parentPath}/${menuPath}`
|
||||||
|
console.log('自动生成组件路径:', componentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestData = {
|
||||||
|
parentId: currentParentId.value, // null 表示顶级目录
|
||||||
|
name: formData.label || formData.name,
|
||||||
|
path: menuPath,
|
||||||
|
component: isDirectory ? '/index/index' : componentPath, // 目录用 /index/index
|
||||||
|
title: formData.name,
|
||||||
|
icon: formData.icon,
|
||||||
|
sort: formData.sort || 0,
|
||||||
|
isHide: formData.isHide || false,
|
||||||
|
keepAlive: formData.keepAlive || false,
|
||||||
|
link: formData.link,
|
||||||
|
isIframe: formData.isIframe || false,
|
||||||
|
roles: formData.roles,
|
||||||
|
autoCreateComponent: !isDirectory // 子菜单自动创建组件文件
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('创建菜单请求数据:', requestData, 'isDirectory:', isDirectory, 'currentParentId:', currentParentId.value)
|
||||||
|
|
||||||
|
await fetchCreateMenu(requestData)
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
getMenuList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存失败:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除菜单
|
* 删除菜单
|
||||||
|
* @param row 菜单行数据
|
||||||
*/
|
*/
|
||||||
const handleDeleteMenu = async (): Promise<void> => {
|
const handleDeleteMenu = async (row: AppRouteRecord): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm('确定要删除该菜单吗?删除后无法恢复', '提示', {
|
await ElMessageBox.confirm('确定要删除该菜单吗?删除后无法恢复', '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
ElMessage.success('删除成功')
|
if (row.id) {
|
||||||
getMenuList()
|
await fetchDeleteMenu(row.id)
|
||||||
} catch (error) {
|
getMenuList()
|
||||||
if (error !== 'cancel') {
|
}
|
||||||
ElMessage.error('删除失败')
|
} catch (error: any) {
|
||||||
|
if (error !== 'cancel' && error?.message !== 'cancel') {
|
||||||
|
// 错误已由 http 工具处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,31 @@
|
|||||||
class="menu-dialog"
|
class="menu-dialog"
|
||||||
@closed="handleClosed"
|
@closed="handleClosed"
|
||||||
>
|
>
|
||||||
|
<!-- 填写说明 -->
|
||||||
|
<ElAlert
|
||||||
|
v-if="!isEdit && form.menuType === 'menu'"
|
||||||
|
:title="isDirectory ? '创建目录说明' : '创建子菜单说明'"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
style="margin-bottom: 16px"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div v-if="isDirectory" style="line-height: 1.8">
|
||||||
|
<div><strong>菜单名称:</strong>显示在侧边栏的名称,如"设备管理"</div>
|
||||||
|
<div><strong>路由地址:</strong>必须以 / 开头,如 /device</div>
|
||||||
|
<div><strong>权限标识:</strong>唯一标识,如 Device(不能与现有的重复)</div>
|
||||||
|
<div><strong>组件路径:</strong>留空即可,系统会自动设置</div>
|
||||||
|
</div>
|
||||||
|
<div v-else style="line-height: 1.8">
|
||||||
|
<div><strong>菜单名称:</strong>显示在侧边栏的名称,如"设备列表"</div>
|
||||||
|
<div><strong>路由地址:</strong>相对路径,不要以 / 开头,如 list</div>
|
||||||
|
<div><strong>权限标识:</strong>唯一标识,如 DeviceList(不能与现有的重复)</div>
|
||||||
|
<div><strong>组件路径:</strong>对应 views 目录下的组件,如 /device/list</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElAlert>
|
||||||
|
|
||||||
<ArtForm
|
<ArtForm
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
v-model="form"
|
v-model="form"
|
||||||
@ -38,7 +63,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormRules } from 'element-plus'
|
import type { FormRules } from 'element-plus'
|
||||||
import { ElIcon, ElTooltip } from 'element-plus'
|
import { ElIcon, ElTooltip, ElAlert } from 'element-plus'
|
||||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||||
import { formatMenuTitle } from '@/utils/router'
|
import { formatMenuTitle } from '@/utils/router'
|
||||||
import type { AppRouteRecord } from '@/types/router'
|
import type { AppRouteRecord } from '@/types/router'
|
||||||
@ -101,6 +126,7 @@
|
|||||||
editData?: AppRouteRecord | any
|
editData?: AppRouteRecord | any
|
||||||
type?: 'menu' | 'button'
|
type?: 'menu' | 'button'
|
||||||
lockType?: boolean
|
lockType?: boolean
|
||||||
|
isDirectory?: boolean // 是否是创建目录模式
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
@ -111,7 +137,8 @@
|
|||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
visible: false,
|
visible: false,
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
lockType: false
|
lockType: false,
|
||||||
|
isDirectory: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<Emits>()
|
const emit = defineEmits<Emits>()
|
||||||
|
|||||||
@ -57,11 +57,39 @@ public class MenuController : ControllerBase
|
|||||||
[HttpPost("api/menu")]
|
[HttpPost("api/menu")]
|
||||||
public async Task<ActionResult<ApiResponse<Menu>>> CreateMenu([FromBody] CreateMenuRequest request)
|
public async Task<ActionResult<ApiResponse<Menu>>> CreateMenu([FromBody] CreateMenuRequest request)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"[CreateMenu] 收到请求: ParentId={request.ParentId}, Name={request.Name}, Path={request.Path}, Component={request.Component}");
|
||||||
|
|
||||||
|
// 处理路径:子菜单(有 ParentId)的路径不能以 / 开头
|
||||||
|
var menuPath = request.Path;
|
||||||
|
if (request.ParentId.HasValue && menuPath.StartsWith("/"))
|
||||||
|
{
|
||||||
|
// 子菜单:取最后一段路径作为相对路径
|
||||||
|
var pathParts = menuPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
menuPath = pathParts.Length > 0 ? pathParts[^1] : menuPath.TrimStart('/');
|
||||||
|
Console.WriteLine($"[CreateMenu] 子菜单路径已修正: {request.Path} -> {menuPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 目录(无 ParentId)的路径必须以 / 开头
|
||||||
|
if (!request.ParentId.HasValue && !menuPath.StartsWith("/"))
|
||||||
|
{
|
||||||
|
menuPath = "/" + menuPath;
|
||||||
|
Console.WriteLine($"[CreateMenu] 目录路径已修正: {request.Path} -> {menuPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 Name 是否唯一,如果重复则自动生成唯一名称
|
||||||
|
var menuName = request.Name;
|
||||||
|
if (await _context.Menus.AnyAsync(m => m.Name == menuName))
|
||||||
|
{
|
||||||
|
// 生成唯一名称:原名称 + 时间戳
|
||||||
|
menuName = $"{request.Name}_{DateTime.Now:yyyyMMddHHmmss}";
|
||||||
|
Console.WriteLine($"[CreateMenu] 菜单名称已修正(避免重复): {request.Name} -> {menuName}");
|
||||||
|
}
|
||||||
|
|
||||||
var menu = new Menu
|
var menu = new Menu
|
||||||
{
|
{
|
||||||
ParentId = request.ParentId,
|
ParentId = request.ParentId,
|
||||||
Name = request.Name,
|
Name = menuName,
|
||||||
Path = request.Path,
|
Path = menuPath,
|
||||||
Component = request.Component,
|
Component = request.Component,
|
||||||
Title = request.Title,
|
Title = request.Title,
|
||||||
Icon = request.Icon,
|
Icon = request.Icon,
|
||||||
@ -75,9 +103,98 @@ public class MenuController : ControllerBase
|
|||||||
|
|
||||||
_context.Menus.Add(menu);
|
_context.Menus.Add(menu);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
Console.WriteLine($"[CreateMenu] 菜单已创建: Id={menu.Id}, ParentId={menu.ParentId}");
|
||||||
|
|
||||||
|
// 如果有组件路径且不是目录组件,自动创建 Vue 组件文件
|
||||||
|
if (!string.IsNullOrEmpty(request.Component) &&
|
||||||
|
request.Component != "/index/index" &&
|
||||||
|
request.AutoCreateComponent)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CreateVueComponentAsync(request.Component, request.Title ?? request.Name);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[CreateMenu] 创建组件文件失败: {ex.Message}");
|
||||||
|
// 不影响菜单创建,只是记录日志
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(ApiResponse<Menu>.Success(menu, "菜单创建成功"));
|
return Ok(ApiResponse<Menu>.Success(menu, "菜单创建成功"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建 Vue 组件文件
|
||||||
|
/// </summary>
|
||||||
|
private async Task CreateVueComponentAsync(string componentPath, string title)
|
||||||
|
{
|
||||||
|
var configuration = HttpContext.RequestServices.GetRequiredService<IConfiguration>();
|
||||||
|
var viewsPath = configuration["Frontend:ViewsPath"] ?? "../adminSystem/src/views";
|
||||||
|
|
||||||
|
// 组件路径格式: /test/page -> test/page.vue
|
||||||
|
// 统一使用正斜杠,然后转换为系统路径分隔符
|
||||||
|
var relativePath = componentPath.TrimStart('/').Replace('\\', '/');
|
||||||
|
|
||||||
|
// 构建完整路径,确保路径分隔符正确
|
||||||
|
var fullPath = Path.GetFullPath(Path.Combine(viewsPath, $"{relativePath}.vue"));
|
||||||
|
var directory = Path.GetDirectoryName(fullPath);
|
||||||
|
|
||||||
|
Console.WriteLine($"[CreateVueComponent] viewsPath: {viewsPath}");
|
||||||
|
Console.WriteLine($"[CreateVueComponent] relativePath: {relativePath}");
|
||||||
|
Console.WriteLine($"[CreateVueComponent] fullPath: {fullPath}");
|
||||||
|
Console.WriteLine($"[CreateVueComponent] directory: {directory}");
|
||||||
|
|
||||||
|
// 如果文件已存在,不覆盖
|
||||||
|
if (System.IO.File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[CreateVueComponent] 组件文件已存在: {fullPath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建目录
|
||||||
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[CreateVueComponent] 创建目录: {directory}");
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成组件名称 (PascalCase)
|
||||||
|
var componentName = string.Join("", relativePath.Split('/', '-', '_')
|
||||||
|
.Where(s => !string.IsNullOrEmpty(s))
|
||||||
|
.Select(s => char.ToUpper(s[0]) + s.Substring(1)));
|
||||||
|
|
||||||
|
// 用于 CSS 类名的路径(使用连字符)
|
||||||
|
var cssClassName = relativePath.Replace('/', '-').Replace('_', '-');
|
||||||
|
|
||||||
|
// 生成 Vue 组件模板
|
||||||
|
var template = $@"<template>
|
||||||
|
<div class=""{cssClassName}-page"">
|
||||||
|
<ElCard>
|
||||||
|
<template #header>
|
||||||
|
<span>{title}</span>
|
||||||
|
</template>
|
||||||
|
<p>这是 {title} 页面</p>
|
||||||
|
<p>组件路径:{componentPath}</p>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang=""ts"">
|
||||||
|
defineOptions({{ name: '{componentName}' }})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.{cssClassName}-page {{
|
||||||
|
padding: 20px;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
";
|
||||||
|
|
||||||
|
await System.IO.File.WriteAllTextAsync(fullPath, template, System.Text.Encoding.UTF8);
|
||||||
|
Console.WriteLine($"[CreateVueComponent] 组件文件已创建: {fullPath}");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新菜单
|
/// 更新菜单
|
||||||
@ -221,6 +338,7 @@ public class CreateMenuRequest
|
|||||||
public string? Link { get; set; }
|
public string? Link { get; set; }
|
||||||
public bool IsIframe { get; set; }
|
public bool IsIframe { get; set; }
|
||||||
public List<string>? Roles { get; set; }
|
public List<string>? Roles { get; set; }
|
||||||
|
public bool AutoCreateComponent { get; set; } = true; // 是否自动创建组件文件
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -28,5 +28,8 @@
|
|||||||
"Audience": "AmtScannerClient",
|
"Audience": "AmtScannerClient",
|
||||||
"AccessTokenExpirationMinutes": 60,
|
"AccessTokenExpirationMinutes": 60,
|
||||||
"RefreshTokenExpirationDays": 7
|
"RefreshTokenExpirationDays": 7
|
||||||
|
},
|
||||||
|
"Frontend": {
|
||||||
|
"ViewsPath": "../../adminSystem/src/views"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user