280 lines
9.8 KiB
Vue
280 lines
9.8 KiB
Vue
<template>
|
||
<el-dialog
|
||
:title="state.ruleForm.menuId ? $t('common.editBtn') : $t('common.addBtn')"
|
||
v-model="visible"
|
||
width="600"
|
||
:close-on-click-modal="false"
|
||
draggable
|
||
>
|
||
<el-form ref="menuDialogFormRef" :model="state.ruleForm" :rules="dataRules" label-width="100px" v-loading="loading">
|
||
<el-form-item :label="$t('sysmenu.menuType')" prop="menuType">
|
||
<el-radio-group v-model="state.ruleForm.menuType">
|
||
<el-radio border label="0">菜单</el-radio>
|
||
<el-radio border label="1">按钮</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('sysmenu.parentId')" prop="parentId">
|
||
<el-tree-select
|
||
v-model="state.ruleForm.parentId"
|
||
:data="state.parentData"
|
||
:render-after-expand="false"
|
||
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||
class="w100"
|
||
clearable
|
||
check-strictly
|
||
:placeholder="$t('sysmenu.inputParentIdTip')"
|
||
>
|
||
</el-tree-select>
|
||
</el-form-item>
|
||
<el-form-item prop="name">
|
||
<template #label>
|
||
{{ state.ruleForm.menuType === '0' ? t('sysmenu.name') : t('sysmenu.buttonName') }}
|
||
</template>
|
||
<el-input v-model="state.ruleForm.name" clearable :placeholder="$t('sysmenu.inputNameTip')"></el-input>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('sysmenu.path')" prop="path" v-if="state.ruleForm.menuType === '0'">
|
||
<el-input v-model="state.ruleForm.path" :placeholder="$t('sysmenu.inputPathTip')"/>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('sysmenu.permission')" prop="permission" v-if="state.ruleForm.menuType === '1'">
|
||
<template #label>
|
||
{{ t('sysmenu.permission') }}
|
||
<tip content="对应后台接口@PreAuthorize注解入参字符串"></tip>
|
||
</template>
|
||
<el-input v-model="state.ruleForm.permission" maxlength="30" :placeholder="$t('sysmenu.inputPermissionTip')"/>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('sysmenu.sortOrder')" prop="sortOrder">
|
||
<el-input-number v-model="state.ruleForm.sortOrder" :min="0" controls-position="right"/>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('sysmenu.icon')" prop="icon" v-if="state.ruleForm.menuType === '0'">
|
||
<IconSelector :placeholder="$t('sysmenu.inputIconTip')" v-model="state.ruleForm.icon"/>
|
||
</el-form-item>
|
||
<el-row>
|
||
<el-col :span="12">
|
||
<el-form-item prop="keepAlive" v-if="state.ruleForm.menuType === '0'">
|
||
<template #label> {{ $t('sysmenu.keepAlive') }}
|
||
<tip content="组件保留状态,避免重新渲染"/>
|
||
</template>
|
||
<el-radio-group v-model="state.ruleForm.keepAlive">
|
||
<el-radio border label="0">否</el-radio>
|
||
<el-radio border label="1">是</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item prop="visible" v-if="state.ruleForm.menuType === '0'">
|
||
<template #label> {{ $t('sysmenu.visible') }}
|
||
<tip content="左侧菜单树是否显示"/>
|
||
</template>
|
||
<el-radio-group v-model="state.ruleForm.visible">
|
||
<el-radio border label="0">否</el-radio>
|
||
<el-radio border label="1">是</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row class="mt-4">
|
||
<el-col :span="12">
|
||
<el-form-item prop="param" v-if="state.ruleForm.menuType === '0'">
|
||
<template #label> {{ $t('sysmenu.param') }}
|
||
<tip content="多个路径指向同一个组件"/>
|
||
</template>
|
||
<el-radio-group v-model="state.ruleForm.param">
|
||
<el-radio border label="0">否</el-radio>
|
||
<el-radio border label="1">是</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item prop="embedded"
|
||
v-if="state.ruleForm.menuType === '0' && state.ruleForm.path?.startsWith('http')">
|
||
<template #label> {{ $t('sysmenu.embedded') }}
|
||
<tip content="iframe嵌套还是打开独立的Tab"/>
|
||
</template>
|
||
<el-radio-group v-model="state.ruleForm.embedded">
|
||
<el-radio border label="0">否</el-radio>
|
||
<el-radio border label="1">是</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-form-item class="mt-4" :label="$t('sysmenu.component')" prop="component" v-if="state.ruleForm.menuType === '0'
|
||
&& state.ruleForm.param === '1'">
|
||
<el-input v-model="state.ruleForm.component" :placeholder="$t('sysmenu.inputComponentTip')"/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script setup lang="ts" name="systemMenuDialog">
|
||
import {useI18n} from 'vue-i18n';
|
||
import {getObj, pageList, putObj, addObj, validateExist} from '/@/api/admin/menu';
|
||
import {useMessage} from '/@/hooks/message';
|
||
import {rule, validateNull} from "/@/utils/validate";
|
||
import Tip from "/@/components/Tip/index.vue";
|
||
|
||
// 定义子组件向父组件传值/事件
|
||
const emit = defineEmits(['refresh']);
|
||
const {t} = useI18n();
|
||
// 引入组件
|
||
const IconSelector = defineAsyncComponent(() => import('/@/components/IconSelector/index.vue'));
|
||
|
||
// 定义变量内容
|
||
const visible = ref(false);
|
||
const loading = ref(false);
|
||
const menuDialogFormRef = ref();
|
||
const originalName = ref(''); // To store the original menu name for comparison during edits
|
||
// 定义需要的数据
|
||
const state = reactive({
|
||
ruleForm: {
|
||
menuId: '',
|
||
name: '',
|
||
permission: '',
|
||
parentId: '',
|
||
icon: '',
|
||
path: '',
|
||
param: '0',
|
||
component: '',
|
||
sortOrder: 0,
|
||
menuType: '1',
|
||
keepAlive: '0',
|
||
visible: '1',
|
||
embedded: '0',
|
||
},
|
||
parentData: [] as any[], // 上级菜单数据
|
||
});
|
||
|
||
// 表单校验规则
|
||
const dataRules = reactive({
|
||
menuType: [{required: true, message: '菜单类型不能为空', trigger: 'blur'}],
|
||
parentId: [{required: true, message: '上级菜单不能为空', trigger: 'blur'}],
|
||
name: [{validator: rule.overLength, trigger: 'blur'}, {
|
||
required: true,
|
||
message: '菜单不能为空',
|
||
trigger: 'blur'
|
||
}, {
|
||
validator: (rule: any, value: any, callback: any) => {
|
||
// 如果是按钮类型菜单,跳过名称唯一性校验
|
||
if (state.ruleForm.menuType === '1') {
|
||
callback();
|
||
return;
|
||
}
|
||
// 如果是编辑状态且菜单名称未改变,跳过校验
|
||
if (state.ruleForm.menuId !== '' && value === originalName.value) {
|
||
callback();
|
||
return;
|
||
}
|
||
// 其他情况下,验证菜单名称唯一性
|
||
validateExist(rule, value, callback, false);
|
||
},
|
||
trigger: 'blur',
|
||
}],
|
||
path: [{validator: rule.overLength, trigger: 'blur'}, {required: true, message: '路径不能为空', trigger: 'blur'}],
|
||
permission: [{validator: rule.overLength, trigger: 'blur'}, {
|
||
required: true,
|
||
message: '权限代码不能为空',
|
||
trigger: 'blur'
|
||
}, {
|
||
validator: (rule: any, value: any, callback: any) => {
|
||
validateExist(rule, value, callback, state.ruleForm.menuId !== '');
|
||
},
|
||
trigger: 'blur',
|
||
}],
|
||
sortOrder: [{required: true, message: '排序不能为空', trigger: 'blur'}],
|
||
component: [{min: 5, max: 255, message: '组件名称长度必须介于 5 和 255 之间', trigger: 'blur'},
|
||
{
|
||
validator: (rule: any, value: any, callback: any) => {
|
||
if (state.ruleForm.menuType === '0' && state.ruleForm.param === '1' && validateNull(state.ruleForm.component)) {
|
||
callback(new Error('请输入组件名称'));
|
||
} else {
|
||
return callback();
|
||
}
|
||
},
|
||
trigger: 'blur',
|
||
}],
|
||
});
|
||
|
||
// 打开弹窗
|
||
const openDialog = (type: string, row?: any) => {
|
||
state.ruleForm.menuId = '';
|
||
visible.value = true;
|
||
originalName.value = ''; // Reset the original name
|
||
|
||
nextTick(() => {
|
||
menuDialogFormRef.value?.resetFields();
|
||
state.ruleForm.parentId = row?.id || '-1';
|
||
});
|
||
|
||
if (row?.id && type === 'edit') {
|
||
state.ruleForm.menuId = row.id;
|
||
// 获取当前节点菜单信息
|
||
getMenuDetail(row.id);
|
||
}
|
||
// 渲染上级菜单列表树
|
||
getAllMenuData();
|
||
};
|
||
|
||
// 获取菜单节点的详细信息
|
||
const getMenuDetail = (id: string) => {
|
||
getObj({menuId: id}).then((res) => {
|
||
if (res.data[0].component) {
|
||
state.ruleForm.param = '1'
|
||
}
|
||
originalName.value = res.data[0].name; // Store the original name
|
||
Object.assign(state.ruleForm, res.data[0]);
|
||
});
|
||
};
|
||
|
||
// 从后端获取菜单信息(含层级)
|
||
const getAllMenuData = () => {
|
||
state.parentData = [];
|
||
pageList({
|
||
type: '0',
|
||
}).then((res) => {
|
||
let menu = {
|
||
id: '-1',
|
||
name: '根菜单',
|
||
children: [],
|
||
};
|
||
menu.children = res.data;
|
||
state.parentData.push(menu);
|
||
});
|
||
};
|
||
|
||
// 保存数据
|
||
const onSubmit = async () => {
|
||
// 立即设置 loading,防止重复点击
|
||
if (loading.value) return;
|
||
loading.value = true;
|
||
|
||
try {
|
||
const valid = await menuDialogFormRef.value.validate().catch(() => {
|
||
});
|
||
if (!valid) {
|
||
loading.value = false;
|
||
return false;
|
||
}
|
||
|
||
state.ruleForm.menuId ? await putObj(state.ruleForm) : await addObj(state.ruleForm);
|
||
useMessage().success(t(state.ruleForm.menuId ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||
visible.value = false;
|
||
emit('refresh');
|
||
} catch (err: any) {
|
||
useMessage().error(err.msg);
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
// 暴露变量 只有暴漏出来的变量 父组件才能使用
|
||
defineExpose({
|
||
openDialog,
|
||
});
|
||
</script>
|