123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- <template>
- <div class="app-container">
- <div class="search-container">
- <el-form ref="queryFormRef" :model="queryParams" :inline="true">
- <el-form-item label="关键字" prop="keywords">
- <el-input
- v-model="queryParams.keywords"
- placeholder="菜单名称"
- clearable
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery"
- ><template #icon><i-ep-search /></template>搜索</el-button
- >
- <el-button @click="resetQuery">
- <template #icon><i-ep-refresh /></template>
- 重置</el-button
- >
- </el-form-item>
- </el-form>
- </div>
- <el-card shadow="never" class="table-container">
- <template #header>
- <el-button
- v-hasPerm="[ButtonPermKeys.SYSTEM.BTNS.menu_add]"
- type="primary"
- @click="openDialog('0')"
- >
- <template #icon><i-ep-plus /></template>
- 新增</el-button
- >
- </template>
- <el-table
- v-loading="loading"
- :data="menuList"
- highlight-current-row
- row-key="id"
- :expand-row-keys="['1']"
- @row-click="onRowClick"
- :tree-props="{
- children: 'childs',
- hasChildren: 'hasChildren',
- }"
- >
- <el-table-column label="菜单名称" >
- <template #default="scope">
- <svg-icon :icon-class="scope.row.icon" />
- {{ scope.row.menuName }}
- </template>
- </el-table-column>
- <el-table-column label="类型" align="center">
- <template #default="scope">
- <el-tag
- v-if="scope.row.type === MenuTypeEnum.CATALOG"
- type="warning"
- >目录</el-tag
- >
- <el-tag v-if="scope.row.type === MenuTypeEnum.MENU" type="success"
- >菜单</el-tag
- >
- <el-tag v-if="scope.row.type === MenuTypeEnum.BUTTON" type="danger"
- >按钮</el-tag
- >
- <el-tag v-if="scope.row.isFrame" type="info">外链</el-tag>
- </template>
- </el-table-column>
- <el-table-column
- label="路由路径"
- align="left"
- prop="path"
- />
- <el-table-column
- label="组件路径"
- align="left"
- prop="component"
- />
- <el-table-column
- label="权限标识"
- align="center"
- width="200"
- prop="perms"
- />
- <el-table-column label="状态" align="center" width="80">
- <template #default="scope">
- <el-tag v-if="scope.row.visible === 0" type="success">显示</el-tag>
- <el-tag v-else type="info">隐藏</el-tag>
- </template>
- </el-table-column>
- <el-table-column
- label="排序"
- align="center"
- width="80"
- prop="orderNum"
- />
- <el-table-column fixed="right" align="center" label="操作" width="220">
- <template #default="scope">
- <el-button
- v-hasPerm="[ButtonPermKeys.SYSTEM.BTNS.menu_add]"
- v-if="scope.row.type == 0 || scope.row.type == 1"
- type="primary"
- link
- size="small"
- @click.stop="openDialog(scope.row.id)"
- >
- <i-ep-plus />新增
- </el-button>
- <el-button
- v-hasPerm="[ButtonPermKeys.SYSTEM.BTNS.menu_edit]"
- type="primary"
- link
- size="small"
- @click.stop="openDialog(undefined, scope.row.id)"
- >
- <i-ep-edit />编辑
- </el-button>
- <el-button
- v-hasPerm="[ButtonPermKeys.SYSTEM.BTNS.menu_del]"
- type="primary"
- link
- size="small"
- @click.stop="handleDelete(scope.row.id)"
- ><i-ep-delete />
- 删除
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </el-card>
- <el-dialog
- v-model="dialog.visible"
- :header="dialog.title"
- destroy-on-close
- append-to-body
- width="1000px"
- @close="closeDialog"
- top="5vh"
- >
- <el-form
- ref="menuFormRef"
- :model="formData"
- :rules="rules"
- label-width="160px"
- >
- <el-form-item label="父级菜单" prop="parentId">
- <el-tree-select
- v-model="formData.parentId"
- placeholder="选择上级菜单"
- :data="menuOptions"
- filterable
- check-strictly
- :render-after-expand="false"
- :props="defaultProps"
- node-key="id"
- />
- </el-form-item>
- <el-form-item label="菜单名称" prop="menuName">
- <el-input v-model="formData.menuName" placeholder="请输入菜单名称" />
- </el-form-item>
- <el-row>
- <el-col :span="12"
- ><el-form-item label="菜单类型" prop="type">
- <el-radio-group
- v-model="formData.type"
- @change="onMenuTypeChange"
- >
- <el-radio :value="0">目录</el-radio>
- <el-radio :value="1">菜单</el-radio>
- <el-radio :value="2">按钮</el-radio>
- <!-- <el-radio label="EXTLINK">外链</el-radio> -->
- </el-radio-group>
- </el-form-item>
- </el-col>
- <el-col :span="12"
- ><el-form-item
- v-if="formData.type !== MenuTypeEnum.BUTTON"
- prop="visible"
- label="显示状态"
- >
- <el-radio-group v-model="formData.visible">
- <el-radio :value="0">显示</el-radio>
- <el-radio :value="1">隐藏</el-radio>
- </el-radio-group>
- </el-form-item></el-col
- >
- </el-row>
- <el-form-item
- v-if="formData.isFrame === 1"
- label="外链地址"
- prop="path"
- >
- <el-input v-model="formData.path" placeholder="请输入外链完整路径" />
- </el-form-item>
- <el-form-item
- v-if="
- formData.type == MenuTypeEnum.CATALOG ||
- formData.type == MenuTypeEnum.MENU
- "
- label="路由路径"
- prop="path"
- >
- <el-input
- v-model="formData.path"
- placeholder="第一层目录:/path。子菜单:path 。如果是外部链接可以直接输入: https://www.xxx.com/"
- />
- </el-form-item>
- <!-- 组件页面完整路径 -->
- <el-form-item
- v-if="formData.type == MenuTypeEnum.MENU"
- label="页面路径"
- prop="component"
- >
- <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.CATALOG"
- label="根目录始终显示"
- >
- <template #label>
- <div>
- 根目录始终显示
- <el-tooltip placement="bottom" effect="light">
- <template #content
- >是:根目录只有一个子路由显示目录
- <br />否:根目录只有一个子路由不显示目录,只显示子路由
- </template>
- <i-ep-QuestionFilled class="inline-block" />
- </el-tooltip>
- </div>
- </template>
- <el-radio-group v-model="formData.alwaysShow">
- <el-radio :value="1">是</el-radio>
- <el-radio :value="0">否</el-radio>
- </el-radio-group>
- </el-form-item> -->
- <el-row>
- <el-col :span="12">
- <el-form-item label="排序" prop="orderNum">
- <el-input-number
- v-model="formData.orderNum"
- style="width: 100px"
- controls-position="right"
- :min="0"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12"
- ><el-form-item label="是否为外链">
- <el-radio-group v-model="formData.isFrame">
- <el-radio :value="1">是</el-radio>
- <el-radio :value="0">否</el-radio>
- </el-radio-group>
- </el-form-item></el-col
- >
- </el-row>
- <el-row>
- <el-col :span="12">
- <el-form-item label="是否启用">
- <el-radio-group v-model="formData.state">
- <el-radio :value="0">启用</el-radio>
- <el-radio :value="1">禁用</el-radio>
- </el-radio-group>
- </el-form-item></el-col
- >
- <el-col :span="12">
- <el-form-item
- v-if="formData.type === MenuTypeEnum.MENU"
- label="是否缓存"
- >
- <el-radio-group v-model="formData.keepAlive">
- <el-radio :value="1">是</el-radio>
- <el-radio :value="0">否</el-radio>
- </el-radio-group>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <!-- v-if="formData.type == MenuTypeEnum.BUTTON" -->
- <el-col :span="12">
- <!-- 权限标识 -->
- <el-form-item label="权限标识" prop="perms">
- <el-input
- v-model="formData.perms"
- placeholder="与buttonPermission中的相对应"
- />
- </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-col>
- <el-col :span="12" />
- </el-row>
- <el-form-item
- v-if="formData.type == MenuTypeEnum.CATALOG"
- label="跳转路由"
- >
- <el-input v-model="formData.redirect" placeholder="跳转路由" />
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitForm">确 定</el-button>
- <el-button @click="closeDialog">取 消</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- defineOptions({
- // eslint-disable-next-line vue/no-reserved-component-names
- name: "Menu",
- inheritAttrs: false,
- });
- import { MenuQuery, MenuForm, MenuVO } from "@/api/system/menu/types";
- import {
- listMenus,
- getMenuForm,
- getMenuOptions,
- addMenu,
- deleteMenu,
- updateMenu,
- } from "@/api/system/menu";
- import { MenuTypeEnum } from "@/enums/MenuTypeEnum";
- import ButtonPermKeys from "@/common/configs/buttonPermission";
- const queryFormRef = ref(ElForm);
- const menuFormRef = ref(ElForm);
- const loading = ref(false);
- const dialog = reactive({
- title: "",
- visible: false,
- });
- const queryParams = reactive<MenuQuery>({});
- const menuList = ref<MenuVO[]>([]);
- const menuOptions = ref<any>([]);
- const formData = reactive<MenuForm>({
- parentId: "0",
- visible: 0,
- orderNum: 1,
- type: MenuTypeEnum.MENU,
- alwaysShow: 0,
- keepAlive: 0,
- isFrame: 0,
- state: 0,
- });
- const rules = reactive({
- parentId: [{ required: true, message: "请选择顶级菜单", trigger: "blur" }],
- menuName: [{ required: true, message: "请输入菜单名称", trigger: "blur" }],
- type: [{ required: true, message: "请选择菜单类型", trigger: "blur" }],
- path: [{ required: true, message: "请输入路由路径", trigger: "blur" }],
- component: [{ required: true, message: "请输入组件路径", trigger: "blur" }],
- // perms: [{ required: true, message: "请输入标识", trigger: "blur" }],
- });
- const defaultProps = {
- children: "childs",
- label: "menuName",
- };
- // 选择表格的行菜单ID
- const selectedRowMenuId = ref<number | undefined>();
- const menuCacheData = reactive({
- type: 0,
- path: "",
- });
- /**
- * 查询
- */
- function handleQuery() {
- // 重置父组件
- loading.value = true;
- listMenus()
- .then(({ data }) => {
- menuList.value = data;
- let treeData = Array.from(data);
- menuOptions.value = treeData;
- menuOptions.value.unshift({ menuName: "顶级菜单", id: "0" });
- })
- .then(() => {
- loading.value = false;
- });
- }
- /** 重置查询 */
- function resetQuery() {
- queryFormRef.value.resetFields();
- handleQuery();
- }
- /**行点击事件 */
- function onRowClick(row: MenuVO) {
- selectedRowMenuId.value = row.id;
- }
- /**
- * 打开表单弹窗
- *
- * @param parentId 父菜单ID
- * @param menuId 菜单ID
- */
- function openDialog(parentId?: string, menuId?: string) {
- dialog.visible = true;
- if (menuId) {
- dialog.title = "编辑菜单";
- getMenuForm(menuId).then(({ data }) => {
- Object.assign(formData, data);
- // menuCacheData.type = data.type;
- // menuCacheData.path = data.path ?? "";
- });
- } else {
- dialog.title = "新增菜单";
- formData.parentId = parentId;
- }
- }
- /** 菜单类型切换事件处理 */
- function onMenuTypeChange() {
- // 如果菜单类型改变,清空路由路径;未改变在切换后还原路由路径
- if (formData.type !== menuCacheData.type) {
- formData.path = "";
- } else {
- formData.path = menuCacheData.path;
- }
- }
- /** 菜单保存提交 */
- function submitForm() {
- menuFormRef.value.validate((isValid: boolean) => {
- console.log("submitForm", formData);
- if (isValid) {
- const menuId = formData.id;
- if (!formData.component) {
- formData.component = "Layout";
- }
- if (menuId) {
- updateMenu(menuId, formData).then(() => {
- ElMessage.success("修改成功");
- closeDialog();
- handleQuery();
- });
- } else {
- addMenu(formData).then(() => {
- ElMessage.success("新增成功");
- closeDialog();
- handleQuery();
- });
- }
- }
- });
- }
- /** 删除菜单 */
- function handleDelete(menuId: string) {
- if (!menuId) {
- ElMessage.warning("请勾选删除项");
- return false;
- }
- ElMessageBox.confirm("确认删除已选中的数据项?", "警告", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning",
- })
- .then(() => {
- deleteMenu(menuId).then(() => {
- ElMessage.success("删除成功");
- handleQuery();
- });
- })
- .catch(() => ElMessage.info("已取消删除"));
- }
- /** 关闭弹窗 */
- function closeDialog() {
- dialog.visible = false;
- resetForm();
- }
- /** 重置表单 */
- function resetForm() {
- menuFormRef.value.resetFields();
- menuFormRef.value.clearValidate();
- formData.id = undefined;
- formData.parentId = "0";
- formData.visible = 1;
- formData.orderNum = 1;
- formData.perms = undefined;
- formData.component = undefined;
- formData.path = undefined;
- formData.redirect = undefined;
- formData.alwaysShow = undefined;
- formData.keepAlive = undefined;
- }
- onMounted(() => {
- handleQuery();
- });
- </script>
- @/common/configs/buttonPermission
|