Files
cashier-web/src/components/FastMenuConfig/dialog-add.vue
2025-12-31 11:47:15 +08:00

267 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>