cashier-web/src/views/admin/system/menu/index.vue

538 lines
17 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>
<div class="app-container">
<div class="search-bar">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="关键字" prop="title">
<el-input
v-model="queryParams.title"
placeholder="菜单名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
<el-button icon="refresh" @click="handleResetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<el-card shadow="never">
<div class="mb-10px">
<el-button
v-hasPerm="['sys:menu:add']"
type="success"
icon="plus"
@click="handleOpenDialog('0')"
>
新增
</el-button>
</div>
<el-table
v-loading="loading"
:data="menuTableData"
highlight-current-row
row-key="menuId"
:tree-props="{
children: 'children',
hasChildren: 'hasChildren',
}"
@row-click="handleRowClick"
>
<el-table-column label="菜单名称" min-width="100">
<template #default="scope">
{{ scope.row.title }}
</template>
</el-table-column>
<el-table-column label="图标" align="center" width="80">
<template #default="scope">
<template v-if="scope.row.icon && scope.row.icon.startsWith('el-icon')">
<el-icon style="vertical-align: -0.15em">
<component :is="scope.row.icon.replace('el-icon-', '')" />
</el-icon>
</template>
<template v-else-if="scope.row.icon">
<svg-icon :icon-class="scope.row.icon" />
</template>
</template>
</el-table-column>
<el-table-column label="类型" align="center" width="80">
<template #default="scope">
<el-tag
v-if="scope.row.type === MenuTypeEnum.MENU && scope.row.path.startsWith('/')"
type="warning"
>
目录
</el-tag>
<el-tag
v-if="scope.row.type === MenuTypeEnum.MENU && !scope.row.path.startsWith('/')"
type="success"
>
菜单
</el-tag>
<el-tag v-if="scope.row.type === MenuTypeEnum.BUTTON" type="danger">按钮</el-tag>
<el-tag v-if="scope.row.type === MenuTypeEnum.EXTLINK" type="info">外链</el-tag>
</template>
</el-table-column>
<el-table-column label="排序" align="left" width="150" prop="menuSort" />
<el-table-column label="路由路径" align="left" width="250" prop="path" />
<el-table-column label="组件路径" align="left" width="250" prop="component" />
<el-table-column label="外链" align="left" width="80" prop="iFrame">
<template #default="scope">
{{ scope.row.iFrame ? "是" : "否" }}
</template>
</el-table-column>
<el-table-column label="缓存" align="left" width="80" prop="cache">
<template #default="scope">
{{ scope.row.cache ? "是" : "否" }}
</template>
</el-table-column>
<el-table-column label="可见" align="left" width="80" prop="iFrame">
<template #default="scope">
{{ scope.row.hidden ? "否" : "是" }}
</template>
</el-table-column>
<el-table-column label="创建日期" align="left" prop="createTime">
<template #default="scope">
{{ scope.row.createTime }}
</template>
</el-table-column>
<el-table-column fixed="right" align="center" label="操作" width="220">
<template #default="scope">
<el-button
v-if="scope.row.type == 0"
v-hasPerm="['sys:menu:add']"
type="primary"
link
size="small"
icon="plus"
@click.stop="handleOpenDialog(scope.row.menuId)"
>
新增
</el-button>
<el-button
v-hasPerm="['sys:menu:edit']"
type="primary"
link
size="small"
icon="edit"
@click.stop="handleOpenDialog(undefined, scope.row.menuId)"
>
编辑
</el-button>
<el-button
v-hasPerm="['sys:menu:delete']"
type="danger"
link
size="small"
icon="delete"
@click.stop="handleDelete(scope.row.menuId)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-drawer v-model="dialog.visible" :title="dialog.title" size="50%" @close="handleCloseDialog">
<el-form ref="editRequestRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="父级菜单" prop="pid">
<el-tree-select
v-model="formData.pid"
placeholder="选择上级菜单"
:data="menuOptions"
filterable
check-strictly
:render-after-expand="false"
/>
</el-form-item>
<el-form-item label="菜单名称" prop="title">
<el-input v-model="formData.title" placeholder="请输入菜单名称" />
</el-form-item>
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="formData.type" @change="handleMenuTypeChange">
<el-radio :value="0">菜单</el-radio>
<el-radio :value="1">按钮</el-radio>
<el-radio :value="2">接口</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否外链" prop="path">
<el-switch
v-model="formData.iFrame"
:active-value="1"
:inactive-value="0"
active-text="是"
inactive-text="否"
/>
</el-form-item>
<el-form-item v-if="formData.type == MenuTypeEnum.MENU" prop="name">
<template #label>
<div class="flex-y-center">
路由名称
<el-tooltip placement="bottom" effect="light">
<template #content>
如果需要开启缓存,需保证页面 defineOptions 中的 name 与此处一致,建议使用驼峰。
</template>
<el-icon class="ml-1 cursor-pointer">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input v-model="formData.name" placeholder="User" />
</el-form-item>
<el-form-item v-if="formData.type == MenuTypeEnum.MENU" prop="path">
<template #label>
<div class="flex-y-center">
路由路径
<el-tooltip placement="bottom" effect="light">
<template #content>
定义应用中不同页面对应的 URL 路径,目录需以 / 开头,菜单项不用。例如:系统管理目录
/system系统管理下的用户管理菜单 user。
</template>
<el-icon class="ml-1 cursor-pointer">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input
v-if="formData.type == MenuTypeEnum.MENU"
v-model="formData.path"
placeholder="system"
/>
<el-input v-else v-model="formData.path" placeholder="user" />
</el-form-item>
<el-form-item v-if="formData.type == MenuTypeEnum.MENU" prop="component">
<template #label>
<div class="flex-y-center">
组件路径
<el-tooltip placement="bottom" effect="light">
<template #content>
组件页面完整路径,相对于 src/views/,如 system/user/index缺省后缀 .vue
</template>
<el-icon class="ml-1 cursor-pointer">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input v-model="formData.component" placeholder="system/user/index" style="width: 95%">
<template v-if="formData.type == MenuTypeEnum.MENU" #prepend>src/views/</template>
<template v-if="formData.type == MenuTypeEnum.MENU" #append>.vue</template>
</el-input>
</el-form-item>
<el-form-item v-if="formData.type !== MenuTypeEnum.BUTTON" prop="hidden" label="显示状态">
<el-radio-group v-model="formData.hidden">
<el-radio :value="1">隐藏</el-radio>
<el-radio :value="0">显示</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="formData.type === MenuTypeEnum.MENU" label="缓存页面">
<el-radio-group v-model="formData.cache">
<el-radio :value="true">开启</el-radio>
<el-radio :value="false">关闭</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序" prop="menuSort">
<el-input-number
v-model="formData.menuSort"
style="width: 100px"
controls-position="right"
:min="0"
/>
</el-form-item>
<!-- 权限标识 -->
<el-form-item
v-if="formData.type == MenuTypeEnum.BUTTON"
label="权限标识"
prop="permission"
>
<el-input v-model="formData.permission" placeholder="sys:user:add" />
</el-form-item>
<el-form-item v-if="formData.type !== MenuTypeEnum.BUTTON" label="图标" prop="icon">
<!-- 图标选择器 -->
<icon-select v-model="formData.icon" />
</el-form-item>
<!-- <el-form-item v-if="formData.type == MenuTypeEnum.MENU" label="跳转路由">
<el-input v-model="formData.redirect" placeholder="跳转路由" />
</el-form-item> -->
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit">确 定</el-button>
<el-button @click="handleCloseDialog">取 消</el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "SysMenu",
inheritAttrs: false,
});
import MenuAPI, { getListRequest, MenuVO, editRequest } from "@/api/account/menu";
import { MenuTypeEnum } from "@/enums/MenuTypeEnum";
const queryFormRef = ref();
const editRequestRef = ref();
const loading = ref(false);
const dialog = reactive({
title: "新增菜单",
visible: false,
});
// 查询参数
const queryParams = reactive<getListRequest>({});
// 菜单表格数据
const menuTableData = ref<MenuVO[]>([]);
// 顶级菜单下拉选项
const menuOptions = ref<OptionType[]>([]);
// 初始菜单表单数据
const initialeditRequestData = ref<editRequest>({
id: "",
pid: "0",
hidden: 0,
menuSort: 1,
type: 0, // 默认菜单
alwaysShow: false,
cache: 0,
title: "",
icon: "",
iFrame: 0,
permission: "",
path: "",
component: "",
name: "",
});
// 菜单表单数据
const formData = ref({ ...initialeditRequestData.value });
// 表单验证规则
const rules = reactive({
pid: [{ required: false, message: "请选择父级菜单", trigger: "blur" }],
title: [{ required: true, message: "请输入菜单名称", trigger: "blur" }],
type: [{ required: true, message: "请选择菜单类型", trigger: "blur" }],
name: [{ required: false, message: "请输入路由名称", trigger: "blur" }],
path: [{ required: true, message: "请输入路由路径", trigger: "blur" }],
component: [{ required: false, message: "请输入组件路径", trigger: "blur" }],
hidden: [{ required: true, message: "请选择显示状态", trigger: "change" }],
});
// 选择表格的行菜单ID
const selectedMenuId = ref<string | undefined>();
// 查询菜单
function handleQuery() {
loading.value = true;
MenuAPI.getList(queryParams)
.then((data) => {
menuTableData.value = data;
})
.finally(() => {
loading.value = false;
});
}
// 重置查询
function handleResetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
// 行点击事件
function handleRowClick(row: MenuVO) {
selectedMenuId.value = row.id;
}
/**
* 返回格式化后的menus菜单数据
*/
function returnFilterMenuData(menus: MenuVO[]): OptionType[] {
return menus.map((menu) => {
const { title, menuId, children, ...rest } = menu;
return {
value: menuId,
label: title,
children: children && returnFilterMenuData(children),
...rest,
} as OptionType;
});
}
/**
* 打开表单弹窗
*
* @param pid 父菜单ID
* @param menuId 菜单ID
*/
function handleOpenDialog(pid?: string, menuId?: string) {
MenuAPI.getList({})
.then((data) => {
menuOptions.value = [{ value: "0", label: "顶级菜单", children: returnFilterMenuData(data) }];
})
.then(() => {
dialog.visible = true;
if (menuId) {
console.log(menuId);
dialog.title = "编辑菜单";
MenuAPI.get(menuId).then((data) => {
console.log(data);
data.iFrame;
formData.value = {
...data,
id: data.id || "",
pid: data.pid || "0",
cache: data.cache ? 1 : 0,
hidden: data.hidden ? 1 : 0,
component: data.component || "",
icon: data.icon || "",
iFrame: data.iFrame ? 1 : 0,
menuSort: data.menuSort ?? 0, // Ensure menuSort is always a number
name: data.name || "", // Ensure name is always a string
path: data.path || "", // Ensure path is always a string
title: data.title || "", // Ensure title is always a string
type: data.type ?? 0, // Ensure type is always a number
};
});
} else {
dialog.title = "新增菜单";
formData.value.pid = pid ? pid : "0";
}
});
}
// 菜单类型切换
function handleMenuTypeChange() {
// 如果菜单类型改变
if (formData.value.type !== initialeditRequestData.value.type) {
if (formData.value.type === MenuTypeEnum.MENU) {
// 目录切换到菜单时,清空组件路径
if (initialeditRequestData.value.type === MenuTypeEnum.MENU) {
formData.value.component = "";
} else {
// 其他情况,保留原有的组件路径
formData.value.path = initialeditRequestData.value.path;
formData.value.component = initialeditRequestData.value.component;
}
}
}
}
/**
* 提交表单
*/
function handleSubmit() {
editRequestRef.value.validate((isValid: boolean) => {
if (isValid) {
const menuId = formData.value.menuId;
const submitFormData = {
...formData.value,
cache: formData.value.cache ? 1 : 0,
hidden: formData.value.hidden ? 1 : 0,
pid: formData.value.pid == 0 ? undefined : String(formData.value.pid),
};
if (menuId) {
//修改时父级菜单不能为当前菜单
if (formData.value.pid == menuId) {
ElMessage.error("父级菜单不能为当前菜单");
return;
}
MenuAPI.edit(menuId, submitFormData).then(() => {
ElMessage.success("修改成功");
handleCloseDialog();
handleQuery();
});
} else {
MenuAPI.add(submitFormData).then(() => {
ElMessage.success("新增成功");
handleCloseDialog();
handleQuery();
});
}
}
});
}
// 删除菜单
function handleDelete(menuId: number) {
if (!menuId) {
ElMessage.warning("请勾选删除项");
return false;
}
ElMessageBox.confirm("确认删除已选中的数据项?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(
() => {
loading.value = true;
MenuAPI.delete(menuId)
.then(() => {
ElMessage.success("删除成功");
handleQuery();
})
.finally(() => {
loading.value = false;
});
},
() => {
ElMessage.info("已取消删除");
}
);
}
function resetForm() {
editRequestRef.value.resetFields();
editRequestRef.value.clearValidate();
formData.value = {
id: "",
pid: "0",
hidden: 0,
menuSort: 1,
type: 0, // 默认菜单
alwaysShow: false,
cache: 0,
title: "",
icon: "",
iFrame: 0,
permission: "",
path: "",
component: "",
name: "",
};
}
// 关闭弹窗
function handleCloseDialog() {
dialog.visible = false;
resetForm();
}
onMounted(() => {
handleQuery();
});
</script>