mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-11-09 13:53:38 +08:00
feat: 添加系统管理-菜单管理 (#929)
* feat: 添加系统管理-菜单管理 * chore: update * chore: add Copyright in login page * chore: 将页脚放在一屏可视区 * chore: 依赖更新 * chore: update * chore: update * chore: 更新依赖 * chore: update `husky v9` * style: 适配`el-dialog`样式的更新 * style: update `src/layout/components/search/components/SearchResult.vue` * chore: update * style: update * fix: 修复`ReDialog`中点击取消和确定按钮会触发两次关闭回调 * chore: update * chore: update src/views/system/menu/README.md * chore: update * chore: update * chore: done * chore: update
This commit is contained in:
@@ -43,3 +43,8 @@ export const getRoleList = (data?: object) => {
|
||||
export const getDeptList = (data?: object) => {
|
||||
return http.request<Result>("post", "/dept", { data });
|
||||
};
|
||||
|
||||
/** 获取菜单管理列表 */
|
||||
export const getMenuList = (data?: object) => {
|
||||
return http.request<Result>("post", "/menu", { data });
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 6.7 KiB |
@@ -1,12 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { animates } from "./animate";
|
||||
import { ref, computed, toRef } from "vue";
|
||||
import { cloneDeep } from "@pureadmin/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "ReAnimateSelector"
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择动画"
|
||||
}
|
||||
});
|
||||
|
||||
const inputValue = defineModel({ type: String });
|
||||
|
||||
const searchVal = ref();
|
||||
@@ -74,7 +81,7 @@ function onMouseleave() {
|
||||
<el-select
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择动画"
|
||||
:placeholder="props.placeholder"
|
||||
popper-class="pure-animate-popper"
|
||||
:model-value="inputValue"
|
||||
:filter-method="filterMethod"
|
||||
|
||||
@@ -90,7 +90,7 @@ function handleClose(
|
||||
v-model="options.visible"
|
||||
class="pure-dialog"
|
||||
:fullscreen="fullscreen ? true : options?.fullscreen ? true : false"
|
||||
@close="handleClose(options, index)"
|
||||
@closed="handleClose(options, index)"
|
||||
@opened="eventsCallBack('open', options, index)"
|
||||
@openAutoFocus="eventsCallBack('openAutoFocus', options, index)"
|
||||
@closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)"
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { CSSProperties, VNode, Component } from "vue";
|
||||
type DoneFn = (cancel?: boolean) => void;
|
||||
type EventType = "open" | "close" | "openAutoFocus" | "closeAutoFocus";
|
||||
type ArgsType = {
|
||||
/** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页 */
|
||||
/** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */
|
||||
command: "cancel" | "sure" | "close";
|
||||
};
|
||||
|
||||
@@ -157,7 +157,7 @@ interface DialogOptions extends DialogProps {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** `Dialog` 关闭后的回调(只有点击右上角关闭按钮或者空白页关闭页面时才会触发) */
|
||||
/** `Dialog` 关闭后的回调(只有点击右上角关闭按钮或空白页或按下了esc键关闭页面时才会触发) */
|
||||
close?: ({
|
||||
options,
|
||||
index
|
||||
@@ -165,7 +165,7 @@ interface DialogOptions extends DialogProps {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** `Dialog` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页 */
|
||||
/** `Dialog` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */
|
||||
closeCallBack?: ({
|
||||
options,
|
||||
index,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { IconJson } from "@/components/ReIcon/data";
|
||||
import { cloneDeep, isAllEmpty } from "@pureadmin/utils";
|
||||
import { ref, computed, CSSProperties, toRef, watch } from "vue";
|
||||
import { ref, computed, CSSProperties, watch } from "vue";
|
||||
import Search from "@iconify-icons/ri/search-eye-line";
|
||||
|
||||
type ParameterCSSProperties = (item?: string) => CSSProperties | undefined;
|
||||
|
||||
@@ -30,7 +30,7 @@ import Table from "@iconify-icons/ri/table-line";
|
||||
import Info from "@iconify-icons/ri/file-info-line";
|
||||
import Artboard from "@iconify-icons/ri/artboard-line";
|
||||
addIcon("ubuntuFill", UbuntuFill);
|
||||
addIcon("menu", Menu);
|
||||
addIcon("ep:menu", Menu);
|
||||
addIcon("edit", Edit);
|
||||
addIcon("informationLine", InformationLine);
|
||||
addIcon("setUp", SetUp);
|
||||
@@ -42,14 +42,14 @@ addIcon("listCheck", ListCheck);
|
||||
addIcon("histogram", Histogram);
|
||||
addIcon("ppt", Ppt);
|
||||
addIcon("checkboxCircleLine", CheckboxCircleLine);
|
||||
addIcon("flUser", FlUser);
|
||||
addIcon("role", Role);
|
||||
addIcon("setting", Setting);
|
||||
addIcon("dept", Dept);
|
||||
addIcon("ri:admin-line", FlUser);
|
||||
addIcon("ri:admin-fill", Role);
|
||||
addIcon("ri:settings-3-line", Setting);
|
||||
addIcon("ri:git-branch-line", Dept);
|
||||
addIcon("search", Search);
|
||||
addIcon("lollipop", Lollipop);
|
||||
addIcon("monitor", Monitor);
|
||||
addIcon("tag", Tag);
|
||||
addIcon("ep:lollipop", Lollipop);
|
||||
addIcon("ep:monitor", Monitor);
|
||||
addIcon("ri:bookmark-2-line", Tag);
|
||||
addIcon("table", Table);
|
||||
addIcon("info", Info);
|
||||
addIcon("artboard", Artboard);
|
||||
|
||||
@@ -29,6 +29,10 @@ const props = {
|
||||
columns: {
|
||||
type: Array as PropType<TableColumnList>,
|
||||
default: () => []
|
||||
},
|
||||
isExpandAll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -38,10 +42,10 @@ export default defineComponent({
|
||||
emits: ["refresh"],
|
||||
setup(props, { emit, slots, attrs }) {
|
||||
const size = ref("default");
|
||||
const isExpandAll = ref(true);
|
||||
const loading = ref(false);
|
||||
const checkAll = ref(true);
|
||||
const isIndeterminate = ref(false);
|
||||
const isExpandAll = ref(props.isExpandAll);
|
||||
const filterColumns = cloneDeep(props?.columns).filter(column =>
|
||||
isBoolean(column?.hide)
|
||||
? !column.hide
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface OptionsType {
|
||||
/** 图标属性、样式配置 */
|
||||
iconAttrs?: iconType;
|
||||
/** 值 */
|
||||
value?: string | number;
|
||||
value?: any;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** `tooltip` 提示 */
|
||||
|
||||
@@ -5,14 +5,16 @@ const TITLE = getConfig("Title");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="layout-footer">
|
||||
MIT © 2020-PRESENT
|
||||
<footer
|
||||
class="layout-footer text-[rgba(0,0,0,0.6)] dark:text-[rgba(220,220,242,0.8)]"
|
||||
>
|
||||
Copyright © 2020-present
|
||||
<a
|
||||
class="ml-1 hover:text-primary"
|
||||
class="hover:text-primary"
|
||||
href="https://github.com/pure-admin"
|
||||
target="_blank"
|
||||
>
|
||||
{{ TITLE }}
|
||||
{{ TITLE }}
|
||||
</a>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -24,6 +26,6 @@ const TITLE = getConfig("Title");
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 0 0 8px;
|
||||
color: #c0c4cc;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@ export interface ListItem {
|
||||
datetime: string;
|
||||
type: string;
|
||||
description: string;
|
||||
status?: "" | "success" | "warning" | "info" | "danger";
|
||||
status?: "primary" | "success" | "warning" | "info" | "danger";
|
||||
extra?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ defineExpose({ handleScroll });
|
||||
cursor: pointer;
|
||||
border: 0.1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
transition: font-size 0.16s;
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { useGlobal } from "@pureadmin/utils";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
|
||||
import MenuFold from "@iconify-icons/ri/menu-fold-fill";
|
||||
|
||||
interface Props {
|
||||
@@ -12,7 +13,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
isActive: false
|
||||
});
|
||||
|
||||
const visible = ref(false);
|
||||
const { tooltipEffect } = useNav();
|
||||
|
||||
const iconClass = computed(() => {
|
||||
@@ -42,21 +42,18 @@ const toggleClick = () => {
|
||||
|
||||
<template>
|
||||
<div class="collapse-container">
|
||||
<el-tooltip
|
||||
placement="right"
|
||||
:visible="visible"
|
||||
:effect="tooltipEffect"
|
||||
:content="props.isActive ? '点击折叠' : '点击展开'"
|
||||
>
|
||||
<IconifyIconOffline
|
||||
:icon="MenuFold"
|
||||
:class="[iconClass, themeColor === 'light' ? '' : 'text-primary']"
|
||||
:style="{ transform: props.isActive ? 'none' : 'rotateY(180deg)' }"
|
||||
@click="toggleClick"
|
||||
@mouseenter="visible = true"
|
||||
@mouseleave="visible = false"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<IconifyIconOffline
|
||||
v-tippy="{
|
||||
content: props.isActive ? '点击折叠' : '点击展开',
|
||||
theme: tooltipEffect,
|
||||
hideOnClick: 'toggle',
|
||||
placement: 'right'
|
||||
}"
|
||||
:icon="MenuFold"
|
||||
:class="[iconClass, themeColor === 'light' ? '' : 'text-primary']"
|
||||
:style="{ transform: props.isActive ? 'none' : 'rotateY(180deg)' }"
|
||||
@click="toggleClick"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import path from "path";
|
||||
import { getConfig } from "@/config";
|
||||
import { menuType } from "../../types";
|
||||
import extraIcon from "./extraIcon.vue";
|
||||
import { useDark } from "@pureadmin/utils";
|
||||
import { ReText } from "@/components/ReText";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
@@ -16,7 +15,6 @@ import ArrowLeft from "@iconify-icons/ep/arrow-left-bold";
|
||||
import ArrowRight from "@iconify-icons/ep/arrow-right-bold";
|
||||
|
||||
const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav();
|
||||
const { isDark } = useDark();
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
@@ -143,7 +141,7 @@ function resolvePath(routePath) {
|
||||
<ReText
|
||||
:tippyProps="{
|
||||
offset: [0, -10],
|
||||
theme: !isDark ? tooltipEffect : undefined
|
||||
theme: tooltipEffect
|
||||
}"
|
||||
class="!text-inherit"
|
||||
>
|
||||
@@ -181,7 +179,7 @@ function resolvePath(routePath) {
|
||||
"
|
||||
:tippyProps="{
|
||||
offset: [0, -10],
|
||||
theme: !isDark ? tooltipEffect : undefined
|
||||
theme: tooltipEffect
|
||||
}"
|
||||
:class="{
|
||||
'!text-inherit': true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// 完整版菜单比较多,将 rank 抽离出来,在此方便维护
|
||||
|
||||
const home = 0, // 平台规定只有 home 路由的 rank 才能为 0 ,所以后端在返回 rank 的时候需要从 1 开始哦
|
||||
const home = 0, // 平台规定只有 home 路由的 rank 才能为 0 ,所以后端在返回 rank 的时候需要从非 0 开始
|
||||
components = 1,
|
||||
able = 2,
|
||||
table = 3,
|
||||
|
||||
@@ -5,7 +5,7 @@ export default {
|
||||
path: "/components",
|
||||
redirect: "/components/dialog",
|
||||
meta: {
|
||||
icon: "menu",
|
||||
icon: "ep:menu",
|
||||
title: $t("menus.hscomponents"),
|
||||
rank: components
|
||||
},
|
||||
|
||||
@@ -44,15 +44,23 @@
|
||||
}
|
||||
|
||||
.pure-dialog {
|
||||
.el-dialog__header.show-close {
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
top: 16px;
|
||||
right: 12px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.pure-dialog-svg {
|
||||
color: var(--el-color-info);
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
top: 20px;
|
||||
right: 14px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
.el-dialog__footer {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,17 +156,14 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding-bottom: 10px;
|
||||
width: calc(100% + 32px);
|
||||
padding: 10px 20px;
|
||||
margin: auto -16px -16px;
|
||||
box-shadow:
|
||||
0 -1px 0 0 #e0e3e8,
|
||||
0 -3px 6px 0 rgb(69 98 155 / 12%);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.wave {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 80%;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
|
||||
@@ -6,10 +6,10 @@ defineOptions({
|
||||
name: "AnimateCss"
|
||||
});
|
||||
|
||||
const icon = ref("");
|
||||
const animate = ref("");
|
||||
|
||||
watch(icon, () => {
|
||||
console.log("icon", icon.value);
|
||||
watch(animate, () => {
|
||||
console.log("animate", animate.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -29,6 +29,6 @@ watch(icon, () => {
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<ReAnimateSelector v-model="icon" class="!w-[200px]" />
|
||||
<ReAnimateSelector v-model="animate" class="!w-[200px]" />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@@ -203,7 +203,7 @@ function onCloseCallBackClick() {
|
||||
} else if (args?.command === "sure") {
|
||||
text = "您点击了确定按钮";
|
||||
} else {
|
||||
text = "您点击了右上角关闭按钮或者空白页";
|
||||
text = "您点击了右上角关闭按钮或空白页或按下了esc键";
|
||||
}
|
||||
message(text);
|
||||
},
|
||||
@@ -301,7 +301,9 @@ function onFormOneClick() {
|
||||
} else if (args?.command === "sure") {
|
||||
message(`您点击了确定按钮,当前表单数据为 ${text}`);
|
||||
} else {
|
||||
message(`您点击了右上角关闭按钮或者空白页,当前表单数据为 ${text}`);
|
||||
message(
|
||||
`您点击了右上角关闭按钮或空白页或按下了esc键,当前表单数据为 ${text}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -12,8 +12,8 @@ const checked2 = ref(false);
|
||||
const baseTag = ref("dark");
|
||||
const tagList = ref([
|
||||
{
|
||||
type: "",
|
||||
text: "Default"
|
||||
type: "primary",
|
||||
text: "Primary"
|
||||
},
|
||||
{
|
||||
type: "success",
|
||||
|
||||
@@ -323,6 +323,18 @@ watch(loginDay, value => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="w-full flex-c absolute bottom-3 text-sm text-[rgba(0,0,0,0.6)] dark:text-[rgba(220,220,242,0.8)]"
|
||||
>
|
||||
Copyright © 2020-present
|
||||
<a
|
||||
class="hover:text-primary"
|
||||
href="https://github.com/pure-admin"
|
||||
target="_blank"
|
||||
>
|
||||
{{ title }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -15,19 +15,26 @@ defineOptions({
|
||||
<div class="card-header">组件方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<Auth value="btn_add">
|
||||
<Auth value="permission:btn:add">
|
||||
<el-button plain type="warning">
|
||||
拥有code:'btn_add' 权限可见
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
</Auth>
|
||||
<Auth :value="['btn_edit']">
|
||||
<Auth :value="['permission:btn:edit']">
|
||||
<el-button plain type="primary">
|
||||
拥有code:['btn_edit'] 权限可见
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
</Auth>
|
||||
<Auth :value="['btn_add', 'btn_edit', 'btn_delete']">
|
||||
<Auth
|
||||
:value="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
>
|
||||
<el-button plain type="danger">
|
||||
拥有code:['btn_add', 'btn_edit', 'btn_delete'] 权限可见
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</Auth>
|
||||
</el-space>
|
||||
@@ -38,18 +45,25 @@ defineOptions({
|
||||
<div class="card-header">函数方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-if="hasAuth('btn_add')" plain type="warning">
|
||||
拥有code:'btn_add' 权限可见
|
||||
<el-button v-if="hasAuth('permission:btn:add')" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button v-if="hasAuth(['btn_edit'])" plain type="primary">
|
||||
拥有code:['btn_edit'] 权限可见
|
||||
<el-button v-if="hasAuth(['permission:btn:edit'])" plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasAuth(['btn_add', 'btn_edit', 'btn_delete'])"
|
||||
v-if="
|
||||
hasAuth([
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
])
|
||||
"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['btn_add', 'btn_edit', 'btn_delete'] 权限可见
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
@@ -61,18 +75,23 @@ defineOptions({
|
||||
</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-auth="'btn_add'" plain type="warning">
|
||||
拥有code:'btn_add' 权限可见
|
||||
<el-button v-auth="'permission:btn:add'" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button v-auth="['btn_edit']" plain type="primary">
|
||||
拥有code:['btn_edit'] 权限可见
|
||||
<el-button v-auth="['permission:btn:edit']" plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="['btn_add', 'btn_edit', 'btn_delete']"
|
||||
v-auth="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['btn_add', 'btn_edit', 'btn_delete'] 权限可见
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
@@ -95,7 +95,10 @@ const tableData = [
|
||||
:columns="columns"
|
||||
>
|
||||
<template #tag="{ row }">
|
||||
<el-tag :type="row.tag === 'Home' ? '' : 'success'" disable-transitions>
|
||||
<el-tag
|
||||
:type="row.tag === 'Home' ? null : 'success'"
|
||||
disable-transitions
|
||||
>
|
||||
{{ row.tag }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
@@ -89,7 +89,7 @@ const {
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 32 }"
|
||||
:adaptiveConfig="{ offsetBottom: 45 }"
|
||||
align-whole="center"
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
@@ -111,20 +111,20 @@ const {
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog('新增', { parentId: row.id } as any)"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('修改', row)"
|
||||
>
|
||||
新增
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('修改', row)"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog('新增', { parentId: row.id } as any)"
|
||||
>
|
||||
修改
|
||||
新增
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除部门名称为${row.name}的这条数据`"
|
||||
@@ -150,6 +150,14 @@ const {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table__inner-wrapper::before) {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
|
||||
27
src/views/system/menu/README.md
Normal file
27
src/views/system/menu/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
<!-- 初版,持续完善中 -->
|
||||
|
||||
## 字段含义
|
||||
|
||||
| 字段 | 说明 |
|
||||
| :---------------- | :----------------------------------------------------------- |
|
||||
| `menuType` | 菜单类型(`0`代表菜单、`1`代表`iframe`、`2`代表外链、`3`代表按钮) |
|
||||
| `parentId` | |
|
||||
| `title` | 菜单名称(兼容国际化、非国际化,如果用国际化的写法就必须在根目录的`locales`文件夹下对应添加) |
|
||||
| `name` | 路由名称(必须唯一并且和当前路由`component`字段对应的页面里用`defineOptions`包起来的`name`保持一致) |
|
||||
| `path` | 路由路径 |
|
||||
| `component` | 组件路径(传`component`组件路径,那么`path`可以随便写,如果不传,`component`组件路径会跟`path`保持一致) |
|
||||
| `rank` | 菜单排序(平台规定只有`home`路由的`rank`才能为`0`,所以后端在返回`rank`的时候需要从非`0`开始 [点击查看更多](https://yiming_chang.gitee.io/pure-admin-doc/pages/routerMenu/#%E8%8F%9C%E5%8D%95%E6%8E%92%E5%BA%8F-rank)) |
|
||||
| `redirect` | 路由重定向 |
|
||||
| `icon` | 菜单图标 |
|
||||
| `extraIcon` | 右侧图标 |
|
||||
| `enterTransition` | 进场动画(页面加载动画) |
|
||||
| `leaveTransition` | 离场动画(页面加载动画) |
|
||||
| `activePath` | 菜单激活(将某个菜单激活,主要用于通过`query`或`params`传参的路由,当它们通过配置`showLink: false`后不在菜单中显示,就不会有任何菜单高亮,而通过设置`activePath`指定激活菜单即可获得高亮,`activePath`为指定激活菜单的`path`) |
|
||||
| `auths` | 权限标识(按钮级别权限设置) |
|
||||
| `frameSrc` | 链接地址(需要内嵌的`iframe`链接地址) |
|
||||
| `frameLoading` | 加载动画(内嵌的`iframe`页面是否开启首次加载动画) |
|
||||
| `keepAlive` | 缓存页面(是否缓存该路由页面,开启后会保存该页面的整体状态,刷新后会清空状态) |
|
||||
| `hiddenTag` | 标签页(当前菜单名称或自定义信息禁止添加到标签页) |
|
||||
| `showLink` | 菜单(是否显示该菜单) |
|
||||
| `showParent` | 父级菜单(是否显示父级菜单 [点击查看更多](https://yiming_chang.gitee.io/pure-admin-doc/pages/routerMenu/#%E7%AC%AC%E4%B8%80%E7%A7%8D-%E8%AF%A5%E6%A8%A1%E5%BC%8F%E9%92%88%E5%AF%B9%E7%88%B6%E7%BA%A7%E8%8F%9C%E5%8D%95%E4%B8%8B%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84%E6%83%85%E5%86%B5-%E5%9C%A8%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84-meta-%E5%B1%9E%E6%80%A7%E4%B8%AD%E5%8A%A0%E4%B8%8A-showparent-true-%E5%8D%B3%E5%8F%AF)) |
|
||||
|
||||
326
src/views/system/menu/form.vue
Normal file
326
src/views/system/menu/form.vue
Normal file
@@ -0,0 +1,326 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import ReCol from "@/components/ReCol";
|
||||
import { formRules } from "./utils/rule";
|
||||
import { FormProps } from "./utils/types";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { IconSelect } from "@/components/ReIcon";
|
||||
import Segmented from "@/components/ReSegmented";
|
||||
import ReAnimateSelector from "@/components/ReAnimateSelector";
|
||||
import {
|
||||
menuTypeOptions,
|
||||
showLinkOptions,
|
||||
keepAliveOptions,
|
||||
hiddenTagOptions,
|
||||
showParentOptions,
|
||||
frameLoadingOptions
|
||||
} from "./utils/enums";
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
menuType: 0,
|
||||
higherMenuOptions: [],
|
||||
parentId: 0,
|
||||
title: "",
|
||||
name: "",
|
||||
path: "",
|
||||
component: "",
|
||||
rank: 99,
|
||||
redirect: " ",
|
||||
icon: "",
|
||||
extraIcon: "",
|
||||
enterTransition: "",
|
||||
leaveTransition: "",
|
||||
activePath: "",
|
||||
auths: "",
|
||||
frameSrc: "",
|
||||
frameLoading: true,
|
||||
keepAlive: false,
|
||||
hiddenTag: false,
|
||||
showLink: true,
|
||||
showParent: false
|
||||
})
|
||||
});
|
||||
|
||||
const ruleFormRef = ref();
|
||||
const newFormInline = ref(props.formInline);
|
||||
|
||||
function getRef() {
|
||||
return ruleFormRef.value;
|
||||
}
|
||||
|
||||
defineExpose({ getRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:model="newFormInline"
|
||||
:rules="formRules"
|
||||
label-width="82px"
|
||||
>
|
||||
<el-row :gutter="30">
|
||||
<re-col>
|
||||
<el-form-item label="菜单类型">
|
||||
<Segmented
|
||||
v-model="newFormInline.menuType"
|
||||
:options="menuTypeOptions"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col>
|
||||
<el-form-item label="上级菜单">
|
||||
<el-cascader
|
||||
v-model="newFormInline.parentId"
|
||||
class="w-full"
|
||||
:options="newFormInline.higherMenuOptions"
|
||||
:props="{
|
||||
value: 'id',
|
||||
label: 'title',
|
||||
emitPath: false,
|
||||
checkStrictly: true
|
||||
}"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择上级菜单"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span>{{ transformI18n(data.title) }}</span>
|
||||
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
|
||||
</template>
|
||||
</el-cascader>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="菜单名称" prop="title">
|
||||
<el-input
|
||||
v-model="newFormInline.title"
|
||||
clearable
|
||||
placeholder="请输入菜单名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-if="newFormInline.menuType !== 3" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="路由名称" prop="name">
|
||||
<el-input
|
||||
v-model="newFormInline.name"
|
||||
clearable
|
||||
placeholder="请输入路由名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-if="newFormInline.menuType !== 3" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="路由路径" prop="path">
|
||||
<el-input
|
||||
v-model="newFormInline.path"
|
||||
clearable
|
||||
placeholder="请输入路由路径"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-show="newFormInline.menuType === 0"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="组件路径">
|
||||
<el-input
|
||||
v-model="newFormInline.component"
|
||||
clearable
|
||||
placeholder="请输入组件路径"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="菜单排序">
|
||||
<el-input-number
|
||||
v-model="newFormInline.rank"
|
||||
class="!w-full"
|
||||
:min="1"
|
||||
:max="9999"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-show="newFormInline.menuType === 0"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="路由重定向">
|
||||
<el-input
|
||||
v-model="newFormInline.redirect"
|
||||
clearable
|
||||
placeholder="请输入默认跳转地址"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="菜单图标">
|
||||
<IconSelect v-model="newFormInline.icon" class="w-full" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="右侧图标">
|
||||
<el-input
|
||||
v-model="newFormInline.extraIcon"
|
||||
clearable
|
||||
placeholder="菜单名称右侧的额外图标"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="进场动画">
|
||||
<ReAnimateSelector
|
||||
v-model="newFormInline.enterTransition"
|
||||
placeholder="请选择页面进场加载动画"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="离场动画">
|
||||
<ReAnimateSelector
|
||||
v-model="newFormInline.leaveTransition"
|
||||
placeholder="请选择页面离场加载动画"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col
|
||||
v-show="newFormInline.menuType === 0"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="菜单激活">
|
||||
<el-input
|
||||
v-model="newFormInline.activePath"
|
||||
clearable
|
||||
placeholder="请输入需要激活的菜单"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-if="newFormInline.menuType === 3" :value="12" :xs="24" :sm="24">
|
||||
<!-- 按钮级别权限设置 -->
|
||||
<el-form-item label="权限标识" prop="auths">
|
||||
<el-input
|
||||
v-model="newFormInline.auths"
|
||||
clearable
|
||||
placeholder="请输入权限标识"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col
|
||||
v-show="newFormInline.menuType === 1"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<!-- iframe -->
|
||||
<el-form-item label="链接地址">
|
||||
<el-input
|
||||
v-model="newFormInline.frameSrc"
|
||||
clearable
|
||||
placeholder="请输入 iframe 链接地址"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-if="newFormInline.menuType === 1" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="加载动画">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.frameLoading ? 0 : 1"
|
||||
:options="frameLoadingOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.frameLoading = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="缓存页面">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.keepAlive ? 0 : 1"
|
||||
:options="keepAliveOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.keepAlive = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="标签页">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.hiddenTag ? 1 : 0"
|
||||
:options="hiddenTagOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.hiddenTag = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="菜单">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.showLink ? 0 : 1"
|
||||
:options="showLinkOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.showLink = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="8"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="父级菜单">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.showParent ? 0 : 1"
|
||||
:options="showParentOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.showParent = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
157
src/views/system/menu/index.vue
Normal file
157
src/views/system/menu/index.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useMenu } from "./utils/hook";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
|
||||
defineOptions({
|
||||
name: "SystemMenu"
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
const {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleDelete,
|
||||
handleSelectionChange
|
||||
} = useMenu();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
|
||||
>
|
||||
<el-form-item label="菜单名称:" prop="title">
|
||||
<el-input
|
||||
v-model="form.title"
|
||||
placeholder="请输入菜单名称"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('search')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<PureTableBar
|
||||
title="菜单管理(初版,持续完善中)"
|
||||
:columns="columns"
|
||||
:isExpandAll="false"
|
||||
:tableRef="tableRef?.getTableRef()"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template #buttons>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog()"
|
||||
>
|
||||
新增菜单
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 45 }"
|
||||
align-whole="center"
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('修改', row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-show="row.menuType !== 3"
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog('新增', { parentId: row.id } as any)"
|
||||
>
|
||||
新增
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除菜单名称为${transformI18n(row.title)}的这条数据${row?.children?.length > 0 ? '。注意下级菜单也会一并删除,请谨慎操作' : ''}`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table__inner-wrapper::before) {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
94
src/views/system/menu/utils/enums.ts
Normal file
94
src/views/system/menu/utils/enums.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { OptionsType } from "@/components/ReSegmented";
|
||||
|
||||
const menuTypeOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "菜单",
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: "iframe",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "外链",
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: "按钮",
|
||||
value: 3
|
||||
}
|
||||
];
|
||||
|
||||
const showLinkOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "显示",
|
||||
tip: "会在菜单中显示",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "隐藏",
|
||||
tip: "不会在菜单中显示",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
|
||||
const keepAliveOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "缓存",
|
||||
tip: "会保存该页面的整体状态,刷新后会清空状态",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "不缓存",
|
||||
tip: "不会保存该页面的整体状态",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
|
||||
const hiddenTagOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "允许",
|
||||
tip: "当前菜单名称或自定义信息允许添加到标签页",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: "禁止",
|
||||
tip: "当前菜单名称或自定义信息禁止添加到标签页",
|
||||
value: true
|
||||
}
|
||||
];
|
||||
|
||||
const showParentOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "显示",
|
||||
tip: "会显示父级菜单",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "隐藏",
|
||||
tip: "不会显示父级菜单",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
|
||||
const frameLoadingOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "开启",
|
||||
tip: "有首次加载动画",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "关闭",
|
||||
tip: "无首次加载动画",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
|
||||
export {
|
||||
menuTypeOptions,
|
||||
showLinkOptions,
|
||||
keepAliveOptions,
|
||||
hiddenTagOptions,
|
||||
showParentOptions,
|
||||
frameLoadingOptions
|
||||
};
|
||||
223
src/views/system/menu/utils/hook.tsx
Normal file
223
src/views/system/menu/utils/hook.tsx
Normal file
@@ -0,0 +1,223 @@
|
||||
import editForm from "../form.vue";
|
||||
import { handleTree } from "@/utils/tree";
|
||||
import { message } from "@/utils/message";
|
||||
import { getMenuList } from "@/api/system";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import { reactive, ref, onMounted, h } from "vue";
|
||||
import type { FormItemProps } from "../utils/types";
|
||||
import { cloneDeep, isAllEmpty } from "@pureadmin/utils";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
|
||||
export function useMenu() {
|
||||
const form = reactive({
|
||||
title: ""
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const dataList = ref([]);
|
||||
const loading = ref(true);
|
||||
|
||||
const getMenuType = (type, text = false) => {
|
||||
switch (type) {
|
||||
case 0:
|
||||
return text ? "菜单" : "primary";
|
||||
case 1:
|
||||
return text ? "iframe" : "warning";
|
||||
case 2:
|
||||
return text ? "外链" : "danger";
|
||||
case 3:
|
||||
return text ? "按钮" : "info";
|
||||
}
|
||||
};
|
||||
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "菜单名称",
|
||||
prop: "title",
|
||||
align: "left",
|
||||
cellRenderer: ({ row }) => (
|
||||
<>
|
||||
<span class="inline-block mr-1">
|
||||
{h(useRenderIcon(row.icon), {
|
||||
style: { paddingTop: "1px" }
|
||||
})}
|
||||
</span>
|
||||
<span>{transformI18n(row.title)}</span>
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "菜单类型",
|
||||
prop: "menuType",
|
||||
width: 100,
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag
|
||||
size={props.size}
|
||||
type={getMenuType(row.menuType)}
|
||||
effect="plain"
|
||||
>
|
||||
{getMenuType(row.menuType, true)}
|
||||
</el-tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "路由路径",
|
||||
prop: "path"
|
||||
},
|
||||
{
|
||||
label: "组件路径",
|
||||
prop: "component",
|
||||
formatter: ({ path, component }) =>
|
||||
isAllEmpty(component) ? path : component
|
||||
},
|
||||
{
|
||||
label: "权限标识",
|
||||
prop: "auths"
|
||||
},
|
||||
{
|
||||
label: "排序",
|
||||
prop: "rank",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: "隐藏",
|
||||
prop: "showLink",
|
||||
formatter: ({ showLink }) => (showLink ? "否" : "是"),
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 210,
|
||||
slot: "operation"
|
||||
}
|
||||
];
|
||||
|
||||
function handleSelectionChange(val) {
|
||||
console.log("handleSelectionChange", val);
|
||||
}
|
||||
|
||||
function resetForm(formEl) {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
onSearch();
|
||||
}
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
const { data } = await getMenuList(); // 这里是返回一维数组结构,前端自行处理成树结构,返回格式要求:唯一id加父节点parentId,parentId取父节点id
|
||||
let newData = data;
|
||||
if (!isAllEmpty(form.title)) {
|
||||
// 前端搜索菜单名称
|
||||
newData = newData.filter(item =>
|
||||
transformI18n(item.title).includes(form.title)
|
||||
);
|
||||
}
|
||||
dataList.value = handleTree(newData); // 处理成树结构
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function formatHigherMenuOptions(treeList) {
|
||||
if (!treeList || !treeList.length) return;
|
||||
const newTreeList = [];
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
treeList[i].title = transformI18n(treeList[i].title);
|
||||
formatHigherMenuOptions(treeList[i].children);
|
||||
newTreeList.push(treeList[i]);
|
||||
}
|
||||
return newTreeList;
|
||||
}
|
||||
|
||||
function openDialog(title = "新增", row?: FormItemProps) {
|
||||
addDialog({
|
||||
title: `${title}菜单`,
|
||||
props: {
|
||||
formInline: {
|
||||
menuType: row?.menuType ?? 0,
|
||||
higherMenuOptions: formatHigherMenuOptions(cloneDeep(dataList.value)),
|
||||
parentId: row?.parentId ?? 0,
|
||||
title: row?.title ?? "",
|
||||
name: row?.name ?? "",
|
||||
path: row?.path ?? "",
|
||||
component: row?.component ?? "",
|
||||
rank: row?.rank ?? 99,
|
||||
redirect: row?.redirect ?? "",
|
||||
icon: row?.icon ?? "",
|
||||
extraIcon: row?.extraIcon ?? "",
|
||||
enterTransition: row?.enterTransition ?? "",
|
||||
leaveTransition: row?.leaveTransition ?? "",
|
||||
activePath: row?.activePath ?? "",
|
||||
auths: row?.auths ?? "",
|
||||
frameSrc: row?.frameSrc ?? "",
|
||||
frameLoading: row?.frameLoading ?? true,
|
||||
keepAlive: row?.keepAlive ?? false,
|
||||
hiddenTag: row?.hiddenTag ?? false,
|
||||
showLink: row?.showLink ?? true,
|
||||
showParent: row?.showParent ?? false
|
||||
}
|
||||
},
|
||||
width: "45%",
|
||||
draggable: true,
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(editForm, { ref: formRef }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const FormRef = formRef.value.getRef();
|
||||
const curData = options.props.formInline as FormItemProps;
|
||||
function chores() {
|
||||
message(
|
||||
`您${title}了菜单名称为${transformI18n(curData.title)}的这条数据`,
|
||||
{
|
||||
type: "success"
|
||||
}
|
||||
);
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
}
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
console.log("curData", curData);
|
||||
// 表单规则校验通过
|
||||
if (title === "新增") {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
chores();
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
chores();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
message(`您删除了菜单名称为${transformI18n(row.title)}的这条数据`, {
|
||||
type: "success"
|
||||
});
|
||||
onSearch();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
/** 搜索 */
|
||||
onSearch,
|
||||
/** 重置 */
|
||||
resetForm,
|
||||
/** 新增、修改菜单 */
|
||||
openDialog,
|
||||
/** 删除菜单 */
|
||||
handleDelete,
|
||||
handleSelectionChange
|
||||
};
|
||||
}
|
||||
10
src/views/system/menu/utils/rule.ts
Normal file
10
src/views/system/menu/utils/rule.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { reactive } from "vue";
|
||||
import type { FormRules } from "element-plus";
|
||||
|
||||
/** 自定义表单规则校验 */
|
||||
export const formRules = reactive(<FormRules>{
|
||||
title: [{ required: true, message: "菜单名称为必填项", trigger: "blur" }],
|
||||
name: [{ required: true, message: "路由名称为必填项", trigger: "blur" }],
|
||||
path: [{ required: true, message: "路由路径为必填项", trigger: "blur" }],
|
||||
auths: [{ required: true, message: "权限标识为必填项", trigger: "blur" }]
|
||||
});
|
||||
29
src/views/system/menu/utils/types.ts
Normal file
29
src/views/system/menu/utils/types.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
interface FormItemProps {
|
||||
/** 菜单类型(0代表菜单、1代表iframe、2代表外链、3代表按钮)*/
|
||||
menuType: number;
|
||||
higherMenuOptions: Record<string, unknown>[];
|
||||
parentId: number;
|
||||
title: string;
|
||||
name: string;
|
||||
path: string;
|
||||
component: string;
|
||||
rank: number;
|
||||
redirect: string;
|
||||
icon: string;
|
||||
extraIcon: string;
|
||||
enterTransition: string;
|
||||
leaveTransition: string;
|
||||
activePath: string;
|
||||
auths: string;
|
||||
frameSrc: string;
|
||||
frameLoading: boolean;
|
||||
keepAlive: boolean;
|
||||
hiddenTag: boolean;
|
||||
showLink: boolean;
|
||||
showParent: boolean;
|
||||
}
|
||||
interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
}
|
||||
|
||||
export type { FormItemProps, FormProps };
|
||||
@@ -108,6 +108,7 @@ const {
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
@@ -206,6 +207,10 @@ const {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
|
||||
@@ -150,6 +150,7 @@ const {
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
align-whole="center"
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
@@ -260,6 +261,10 @@ const {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
|
||||
@@ -96,7 +96,7 @@ defineExpose({ onTreeReset });
|
||||
<div
|
||||
v-loading="props.treeLoading"
|
||||
class="h-full bg-bg_color overflow-auto"
|
||||
:style="{ minHeight: `calc(100vh - 133px)` }"
|
||||
:style="{ minHeight: `calc(100vh - 145px)` }"
|
||||
>
|
||||
<div class="flex items-center h-[34px]">
|
||||
<el-input
|
||||
|
||||
@@ -104,7 +104,7 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag
|
||||
size={props.size}
|
||||
type={row.sex === 1 ? "danger" : ""}
|
||||
type={row.sex === 1 ? "danger" : null}
|
||||
effect="plain"
|
||||
>
|
||||
{row.sex === 1 ? "女" : "男"}
|
||||
|
||||
Reference in New Issue
Block a user