267 lines
6.9 KiB
Vue
267 lines
6.9 KiB
Vue
<template>
|
||
<el-dialog :title="form.id ? '编辑' : '添加'" width="400px" v-model="visible" @closed="resetForm">
|
||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" label-position="left">
|
||
<el-form-item label="菜单" prop="menuId">
|
||
<el-tree-select
|
||
:check-strictly="true"
|
||
v-model="form.menuId"
|
||
:data="menus"
|
||
:render-after-expand="false"
|
||
style="width: 240px"
|
||
node-key="menuId"
|
||
:disabled-key="disabled"
|
||
></el-tree-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="排序值">
|
||
<el-input-number v-model="form.sort" :step="1" step-strictly />
|
||
</el-form-item>
|
||
<el-form-item label="启用状态">
|
||
<el-switch v-model="form.status" :active-value="1" :inactive-value="0"></el-switch>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<div class="dialog_footer">
|
||
<div class="btn">
|
||
<el-button size="large" style="width: 100%" @click="visible = false">取 消</el-button>
|
||
</div>
|
||
<div class="btn">
|
||
<el-button
|
||
size="large"
|
||
type="primary"
|
||
:loading="loading"
|
||
style="width: 100%"
|
||
@click="submitHandle"
|
||
>
|
||
确 定
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script setup>
|
||
// 补充缺失的导入
|
||
import { ref, onMounted, watch } from "vue";
|
||
import { ElNotification, ElMessage } from "element-plus"; // 导入提示组件
|
||
import { filterNumberInput } from "@/utils";
|
||
import { roleTemplateAdd } from "@/api/account/roleTemplate";
|
||
import MenuAPI from "@/api/account/menu";
|
||
import quickApi from "@/api/account/quick";
|
||
|
||
const props = defineProps({
|
||
isGetMenu: {
|
||
type: Boolean,
|
||
required: false,
|
||
default: false, // 补充默认值
|
||
},
|
||
parMenus: {
|
||
type: Array,
|
||
required: false,
|
||
default: () => [], // 补充默认值
|
||
},
|
||
item: {
|
||
type: Object,
|
||
required: false,
|
||
default: () => null,
|
||
},
|
||
});
|
||
|
||
const menus = ref([]);
|
||
|
||
/**
|
||
* 格式化菜单数据:核心逻辑 - 禁用有子菜单的父节点,仅允许选叶子节点
|
||
* @param arr 原始菜单数组
|
||
* @returns 格式化后带禁用标记的菜单数组
|
||
*/
|
||
function returnMenu(arr) {
|
||
let result = [];
|
||
for (let menu of arr) {
|
||
// 过滤掉"默认接口目录"
|
||
if (menu.title === "默认接口目录") continue;
|
||
|
||
// 筛选有效子菜单:非隐藏 + 类型为0
|
||
const children = menu.children ? menu.children.filter((v) => !v.hidden && v.type === 0) : [];
|
||
// 递归处理子菜单
|
||
const formattedChildren = returnMenu(children);
|
||
|
||
// 核心:判断是否为叶子节点(无有效子菜单)
|
||
const isLeaf = formattedChildren.length === 0;
|
||
|
||
// 组装节点:有子菜单则禁用(disabled: true),无则允许选择
|
||
const menuNode = {
|
||
...menu,
|
||
label: menu.title,
|
||
value: menu.menuId,
|
||
menuId: menu.menuId, // 保持node-key一致
|
||
disabled: !isLeaf, // 有子菜单 → 禁用,无 → 启用
|
||
children: formattedChildren,
|
||
};
|
||
|
||
// 特殊处理:如果子菜单只有1个,直接扁平化(保持原有逻辑)
|
||
if (formattedChildren.length === 1) {
|
||
result.push({
|
||
...formattedChildren[0],
|
||
disabled: formattedChildren[0].children.length > 0, // 子节点仍判断是否有后代
|
||
});
|
||
} else {
|
||
result.push(menuNode);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
onMounted(async () => {
|
||
if (!props.isGetMenu) return;
|
||
try {
|
||
const res = await MenuAPI.getRoutes();
|
||
menus.value = returnMenu(res);
|
||
} catch (error) {
|
||
ElMessage.error("菜单数据加载失败");
|
||
console.error("getRoutes error:", error);
|
||
}
|
||
});
|
||
|
||
// 监听父组件传入的菜单变化
|
||
watch(
|
||
() => props.parMenus,
|
||
(newVal) => {
|
||
if (newVal.length) {
|
||
menus.value = returnMenu(newVal);
|
||
}
|
||
},
|
||
{ deep: true } // 监听数组内部变化
|
||
);
|
||
|
||
// 对话框显隐绑定
|
||
const visible = defineModel({
|
||
type: Boolean,
|
||
required: false,
|
||
default: false,
|
||
});
|
||
|
||
const formRef = ref(null);
|
||
const loading = ref(false);
|
||
// 表单初始化
|
||
const form = ref({
|
||
menuId: "",
|
||
sort: 0,
|
||
url: "",
|
||
status: 1,
|
||
});
|
||
|
||
/**
|
||
* 自定义校验规则:确保选中的是叶子节点(兜底验证)
|
||
* @param rule 校验规则
|
||
* @param value 选中的menuId
|
||
* @param callback 回调函数
|
||
*/
|
||
function validateLeafMenu(rule, value, callback) {
|
||
if (!value) {
|
||
return callback(new Error("请选择菜单"));
|
||
}
|
||
// 递归查找节点,判断是否为叶子节点
|
||
const findNode = (nodes, menuId) => {
|
||
for (let node of nodes) {
|
||
if (node.menuId === menuId) return node;
|
||
if (node.children) {
|
||
const res = findNode(node.children, menuId);
|
||
if (res) return res;
|
||
}
|
||
}
|
||
return null;
|
||
};
|
||
const selectedNode = findNode(menus.value, value);
|
||
|
||
if (!selectedNode) {
|
||
callback(new Error("选择的菜单不存在"));
|
||
} else if (selectedNode.disabled) {
|
||
callback(new Error("有子菜单的父节点不可选,请选择子菜单"));
|
||
} else {
|
||
callback(); // 校验通过
|
||
}
|
||
}
|
||
|
||
// 表单校验规则:修复拼写错误 + 新增自定义叶子节点校验
|
||
const rules = ref({
|
||
menuId: [
|
||
{
|
||
required: true,
|
||
validator: validateLeafMenu, // 替换原有规则为自定义校验
|
||
trigger: "change", // 选择变化时校验(原triiger拼写错误)
|
||
},
|
||
],
|
||
});
|
||
|
||
// 重置表单
|
||
function resetForm() {
|
||
form.value = {
|
||
menuId: "",
|
||
sort: 0,
|
||
url: "",
|
||
status: 1,
|
||
};
|
||
if (formRef.value) {
|
||
formRef.value.resetFields(); // 避免null调用
|
||
}
|
||
}
|
||
|
||
// 提交事件
|
||
const emits = defineEmits(["success"]);
|
||
async function submitHandle() {
|
||
try {
|
||
// 修复vaild拼写错误
|
||
const valid = await formRef.value.validate();
|
||
if (!valid) return;
|
||
|
||
loading.value = true;
|
||
// 排序值兜底
|
||
form.value.sort = form.value.sort === "" ? 0 : form.value.sort;
|
||
|
||
// 区分新增/编辑
|
||
if (form.value.id) {
|
||
await quickApi.edit(form.value);
|
||
ElNotification({ title: "成功", message: "编辑成功", type: "success" });
|
||
} else {
|
||
await quickApi.add(form.value);
|
||
ElNotification({ title: "成功", message: "添加成功", type: "success" });
|
||
}
|
||
|
||
emits("success");
|
||
visible.value = false;
|
||
} catch (error) {
|
||
ElMessage.error(form.value.id ? "编辑失败" : "添加失败");
|
||
console.error("submit error:", error);
|
||
} finally {
|
||
loading.value = false; // 无论成败都关闭loading
|
||
}
|
||
}
|
||
|
||
// 监听props.item初始化表单
|
||
watch(
|
||
() => props.item,
|
||
(newval) => {
|
||
if (newval) {
|
||
form.value = { ...newval }; // 深拷贝,避免修改原对象
|
||
}
|
||
},
|
||
{ deep: true, immediate: true } // 初始化触发
|
||
);
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.dialog_footer {
|
||
display: flex;
|
||
gap: 14px;
|
||
.btn {
|
||
flex: 1;
|
||
}
|
||
}
|
||
// 可选:美化禁用节点的样式
|
||
.el-select-dropdown__item.is-disabled {
|
||
color: var(--el-text-color-regular);
|
||
cursor: pointer;
|
||
}
|
||
</style>
|