|
|
@@ -12,58 +12,67 @@
|
|
|
<el-form-item label="上级菜单" prop="parentId">
|
|
|
<el-tree-select
|
|
|
rowKey="key"
|
|
|
- :data="getPermissionList"
|
|
|
+ :data="extendedMenuTree"
|
|
|
clearable
|
|
|
check-strictly
|
|
|
v-model="formParams.parentId"
|
|
|
+ :disabled="isEditing"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
|
|
|
+ <el-form-item label="菜单名称" required>
|
|
|
+ <el-tree-select
|
|
|
+ v-if="isCreating"
|
|
|
+ rowKey="key"
|
|
|
+ :data="routeViewList"
|
|
|
+ clearable
|
|
|
+ check-strictly
|
|
|
+ v-model="routeViewValue"
|
|
|
+ />
|
|
|
+ <el-input v-else v-model="formParams.menuName" disabled />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+
|
|
|
<el-row :gutter="24">
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="菜单名称" prop="menuName">
|
|
|
- <el-input placeholder="菜单名称" v-model="formParams.menuName" />
|
|
|
- </el-form-item>
|
|
|
- </el-col>
|
|
|
- <el-col :span="12">
|
|
|
- <el-form-item label="菜单图标" prop="icon">
|
|
|
+ <el-form-item prop="routeUrl">
|
|
|
<template #label>
|
|
|
<div class="flex items-center">
|
|
|
<el-tooltip trigger="hover">
|
|
|
<el-icon :size="18" class="mr-1 text-gray-400 cursor-pointer">
|
|
|
<QuestionCircleOutlined />
|
|
|
</el-icon>
|
|
|
- <template #content>
|
|
|
- 菜单图标,填写图标组件名称,需在 `src\router\router-icons.ts` 中导入并映射
|
|
|
- </template>
|
|
|
+ <template #content> 路由地址,如:user </template>
|
|
|
</el-tooltip>
|
|
|
- <span>菜单图标</span>
|
|
|
+ <span>路由地址</span>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <el-input placeholder="菜单图标映射名称" v-model="formParams.icon" />
|
|
|
+ <el-input placeholder="路由地址" v-model="formParams.routeUrl" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <el-row :gutter="24">
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item prop="routeUrl">
|
|
|
+ <el-form-item prop="routeName">
|
|
|
<template #label>
|
|
|
<div class="flex items-center">
|
|
|
<el-tooltip trigger="hover">
|
|
|
<el-icon :size="18" class="mr-1 text-gray-400 cursor-pointer">
|
|
|
<QuestionCircleOutlined />
|
|
|
</el-icon>
|
|
|
- <template #content> 路由地址,如:user </template>
|
|
|
+ <template #content>
|
|
|
+ 对应路由配置文件中 `name` 只能是唯一性,配置 `http(s)://` 开头地址 则会新窗口打开
|
|
|
+ </template>
|
|
|
</el-tooltip>
|
|
|
- <span>路由地址</span>
|
|
|
+ <span>路由名称</span>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <el-input placeholder="路由地址" v-model="formParams.routeUrl" />
|
|
|
+ <el-input placeholder="路由名称" v-model="formParams.routeName" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
- <el-col :span="12">
|
|
|
- <el-form-item prop="routeName">
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="24">
|
|
|
+ <el-col>
|
|
|
+ <el-form-item prop="component">
|
|
|
<template #label>
|
|
|
<div class="flex items-center">
|
|
|
<el-tooltip trigger="hover">
|
|
|
@@ -71,20 +80,21 @@
|
|
|
<QuestionCircleOutlined />
|
|
|
</el-icon>
|
|
|
<template #content>
|
|
|
- 对应路由配置文件中 `name` 只能是唯一性,配置 `http(s)://` 开头地址 则会新窗口打开
|
|
|
+ 访问的组件路径,如:`/system/menu/menu`,默认在`views`目录下,默认 `LAYOUT`
|
|
|
+ 如果是多级菜单 `ParentLayout`
|
|
|
</template>
|
|
|
</el-tooltip>
|
|
|
- <span>路由名称</span>
|
|
|
+ <span>组件路径</span>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <el-input placeholder="路由名称" v-model="formParams.routeName" />
|
|
|
+ <el-input placeholder="组件路径" v-model="formParams.component" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
<el-row :gutter="24">
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item prop="redirect">
|
|
|
+ <el-form-item label="菜单图标" prop="icon">
|
|
|
<template #label>
|
|
|
<div class="flex items-center">
|
|
|
<el-tooltip trigger="hover">
|
|
|
@@ -92,25 +102,17 @@
|
|
|
<QuestionCircleOutlined />
|
|
|
</el-icon>
|
|
|
<template #content>
|
|
|
- 默认跳转路由地址,如:`/system/menu/menu` 多级路由情况下适用
|
|
|
+ 菜单图标,填写图标组件名称,需在 `src\router\router-icons.ts` 中导入并映射
|
|
|
</template>
|
|
|
</el-tooltip>
|
|
|
- <span>默认路由</span>
|
|
|
+ <span>菜单图标</span>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <el-input placeholder="默认跳转路由地址" v-model="formParams.redirect" />
|
|
|
+ <el-input placeholder="菜单图标映射名称" v-model="formParams.icon" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="显示排序" prop="orderNum">
|
|
|
- <el-input-number placeholder="显示排序" v-model="formParams.orderNum" class="w-full" />
|
|
|
- </el-form-item>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <el-row :gutter="24">
|
|
|
- <el-col>
|
|
|
- <el-form-item prop="component">
|
|
|
+ <el-form-item prop="redirect">
|
|
|
<template #label>
|
|
|
<div class="flex items-center">
|
|
|
<el-tooltip trigger="hover">
|
|
|
@@ -118,18 +120,29 @@
|
|
|
<QuestionCircleOutlined />
|
|
|
</el-icon>
|
|
|
<template #content>
|
|
|
- 访问的组件路径,如:`/system/menu/menu`,默认在`views`目录下,默认 `LAYOUT`
|
|
|
- 如果是多级菜单 `ParentLayout`
|
|
|
+ 默认跳转路由地址,如:`/system/menu/menu` 多级路由情况下适用
|
|
|
</template>
|
|
|
</el-tooltip>
|
|
|
- <span>组件路径</span>
|
|
|
+ <span>默认路由</span>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <el-input placeholder="组件路径" v-model="formParams.component" />
|
|
|
+ <el-input placeholder="默认跳转路由地址" v-model="formParams.redirect" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+ <el-row :gutter="24">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="显示排序" prop="orderNum">
|
|
|
+ <el-input-number placeholder="显示排序" v-model="formParams.orderNum" class="w-full" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
<el-divider border-style="dashed" class="mb-10">功能设置</el-divider>
|
|
|
|
|
|
<el-row :gutter="24">
|
|
|
@@ -268,28 +281,59 @@
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
- import { ref, computed } from 'vue';
|
|
|
- import { ElMessage, FormInstance } from 'element-plus';
|
|
|
+ import { ref, computed, watch, shallowRef } from 'vue';
|
|
|
+ import { ElMessage, FormInstance, FormRules } from 'element-plus';
|
|
|
import { addMenu, editMenu } from '@/api/system/menu';
|
|
|
import { QuestionCircleOutlined } from '@vicons/antd';
|
|
|
import { replaceParams } from '@/utils/helper/treeHelper';
|
|
|
import { cloneDeep } from 'lodash-es';
|
|
|
- import { MenuDetailItem } from '@/types/menu/type';
|
|
|
+ import { MenuDetailItem, MenuTree, Menu } from '@/types/menu/type';
|
|
|
+ import { getChildRoutesView, _RouteViewItem, getRouteByName } from '@/router/full-routes';
|
|
|
+ import { getTreeItem } from '@/utils';
|
|
|
+
|
|
|
+ const props = defineProps<{
|
|
|
+ parentMenuTree: MenuTree; // "上级菜单"树
|
|
|
+ isShowSubmit?: boolean;
|
|
|
+ isCreating?: boolean;
|
|
|
+ }>();
|
|
|
|
|
|
const emit = defineEmits(['change']);
|
|
|
|
|
|
- const props = defineProps({
|
|
|
- permissionList: {
|
|
|
- type: Array,
|
|
|
- defalut: () => [],
|
|
|
+ const formRef = ref<FormInstance>();
|
|
|
+ const rules: FormRules = {
|
|
|
+ parentId: {
|
|
|
+ required: true,
|
|
|
+ message: '上级菜单',
|
|
|
+ trigger: 'change',
|
|
|
},
|
|
|
- isShowSubmit: {
|
|
|
- type: Boolean,
|
|
|
- defalut: false,
|
|
|
+ menuName: {
|
|
|
+ required: true,
|
|
|
+ message: '菜单名称',
|
|
|
+ trigger: 'blur',
|
|
|
},
|
|
|
- });
|
|
|
+ routeUrl: {
|
|
|
+ required: true,
|
|
|
+ message: '路由地址不能为空',
|
|
|
+ trigger: 'blur',
|
|
|
+ },
|
|
|
+ component: {
|
|
|
+ required: true,
|
|
|
+ message: '组件路径不能为空',
|
|
|
+ trigger: 'blur',
|
|
|
+ },
|
|
|
+ routeName: {
|
|
|
+ required: true,
|
|
|
+ message: '路由名称不能为空',
|
|
|
+ trigger: 'blur',
|
|
|
+ },
|
|
|
+ frameSrc: {
|
|
|
+ required: true,
|
|
|
+ message: '外部地址不能为空',
|
|
|
+ trigger: 'blur',
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
|
|
|
- const formRef = ref<FormInstance>();
|
|
|
const subLoading = ref(false);
|
|
|
|
|
|
const defaultFormParams: MenuDetailItem = {
|
|
|
@@ -314,52 +358,67 @@
|
|
|
openType: 1,
|
|
|
query: '',
|
|
|
};
|
|
|
-
|
|
|
const formParams = ref({ ...defaultFormParams });
|
|
|
|
|
|
- const getPermissionList = computed(() => {
|
|
|
+ const extendedMenuTree = computed(() => {
|
|
|
// 可根据需要是否需要嵌套这个 避免出现选择器出现 0 的情况
|
|
|
return [
|
|
|
{
|
|
|
label: '顶级目录',
|
|
|
value: 0,
|
|
|
- children: replaceParams(cloneDeep(props.permissionList || []), 'label', 'key'),
|
|
|
+ children: replaceParams(cloneDeep(props.parentMenuTree || []), 'label', 'id'),
|
|
|
},
|
|
|
];
|
|
|
});
|
|
|
|
|
|
- const rules = {
|
|
|
- parentId: {
|
|
|
- required: true,
|
|
|
- message: '上级菜单',
|
|
|
- trigger: 'change',
|
|
|
- },
|
|
|
- menuName: {
|
|
|
- required: true,
|
|
|
- message: '菜单名称',
|
|
|
- trigger: 'blur',
|
|
|
- },
|
|
|
- routeUrl: {
|
|
|
- required: true,
|
|
|
- message: '路由地址不能为空',
|
|
|
- trigger: 'blur',
|
|
|
- },
|
|
|
- component: {
|
|
|
- required: true,
|
|
|
- message: '组件路径不能为空',
|
|
|
- trigger: 'blur',
|
|
|
- },
|
|
|
- routeName: {
|
|
|
- required: true,
|
|
|
- message: '路由名称不能为空',
|
|
|
- trigger: 'blur',
|
|
|
- },
|
|
|
- frameSrc: {
|
|
|
- required: true,
|
|
|
- message: '外部地址不能为空',
|
|
|
- trigger: 'blur',
|
|
|
- },
|
|
|
- };
|
|
|
+ const isEditing = computed(() => !props.isCreating);
|
|
|
+ const routeViewValue = ref('');
|
|
|
+ const routeViewList = shallowRef<_RouteViewItem[]>([]);
|
|
|
+
|
|
|
+
|
|
|
+ // 根据 "上级菜单",生成可选的"当前菜单"
|
|
|
+ watch(
|
|
|
+ () => formParams.value.parentId,
|
|
|
+ (newVal) => {
|
|
|
+ // 编辑模式下,只是展示,不需要计算数据。
|
|
|
+ if (isEditing.value) return;
|
|
|
+
|
|
|
+ // 若清除选项
|
|
|
+ if (newVal == null) {
|
|
|
+ routeViewList.value = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 若选择顶级目录, 返回路由表的 leve1 列表
|
|
|
+ if (newVal === 0) {
|
|
|
+ routeViewList.value = getChildRoutesView(null);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 否则展示 parent 下的 子路由
|
|
|
+ const { routeName } = getTreeItem(props.parentMenuTree!, newVal, 'id') as Menu;
|
|
|
+ if (routeName) {
|
|
|
+ routeViewList.value = getChildRoutesView(routeName);
|
|
|
+ } else {
|
|
|
+ ElMessage.error('无法获取可用路由列表');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ // 根据 routeViewValue,即 routeName,将菜单其余信息补充
|
|
|
+ watch(routeViewValue, (val) => {
|
|
|
+ if (!val) {
|
|
|
+ handleReset();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const currentRoute = getRouteByName(val);
|
|
|
+ formParams.value.routeName = currentRoute.name;
|
|
|
+ formParams.value.routeUrl = currentRoute.path;
|
|
|
+ formParams.value.component = currentRoute.component;
|
|
|
+ formParams.value.icon = currentRoute.meta.icon as string;
|
|
|
+ formParams.value.menuName = currentRoute.meta.title as string;
|
|
|
+ });
|
|
|
|
|
|
function setData(data: MenuDetailItem) {
|
|
|
formParams.value = data;
|
|
|
@@ -402,7 +461,8 @@
|
|
|
|
|
|
function handleReset() {
|
|
|
formRef.value?.resetFields();
|
|
|
- formParams.value = Object.assign(formParams.value, defaultFormParams);
|
|
|
+ formParams.value = { ...defaultFormParams };
|
|
|
+ routeViewValue.value = '';
|
|
|
}
|
|
|
|
|
|
defineExpose({
|