Files
vue3-element-admin/src/views/system/menu/components/Menu.vue

413 lines
11 KiB
Vue

<template>
<div class="component-container">
<!-- 搜索表单 -->
<el-form
ref="queryFormRef"
:model="queryParams"
:inline="true"
>
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
<el-form-item prop="name">
<el-input
v-model="queryParams.name"
placeholder="菜单名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="menuList"
row-key="id"
highlight-current-row
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
@row-click="handleRowClick"
border
>
<el-table-column label="菜单名称">
<template #default="scope">
<svg-icon color='#333' :icon-class="scope.row.icon?scope.row.icon:'build'"/>
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="状态" align="center" width="80">
<template #default="scope">
<el-tag v-if="scope.row.visible===1" type="success">显示</el-tag>
<el-tag v-else type="info">隐藏</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="130">
<template #default="scope">
<el-button
type="success"
:icon="Plus"
circle
plain
@click.stop="handleAdd(scope.row)"
/>
<el-button
type="primary"
:icon="Edit"
circle
plain
@click.stop="handleUpdate(scope.row)"
/>
<el-button
type="danger"
:icon="Delete"
circle
plain
@click.stop="handleDelete(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<!-- 弹窗表单 -->
<el-dialog
:title="dialog.title"
v-model="dialog.visible"
width="750px"
>
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="父级菜单" prop="parentId">
<tree-select
v-model="formData.parentId"
:options="menuOptions"
placeholder="选择上级菜单"
/>
</el-form-item>
<el-form-item label="菜单名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入菜单名称"/>
</el-form-item>
<el-form-item label="是否外链">
<el-radio-group v-model="isExternalPath">
<el-radio :label="false"></el-radio>
<el-radio :label="true"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="isExternalPath" label="外链地址" prop="path">
<el-input v-model="formData.path" placeholder="请输入外链完整路径"/>
</el-form-item>
<el-form-item v-if="!isExternalPath" label="页面路径" prop="component">
<el-input
v-model="formData.component"
placeholder="system/user/index"
style="width: 95%"
>
<template v-if="formData.parentId!=0" #prepend>src/views/</template>
<template v-if="formData.parentId!=0" #append>.vue</template>
</el-input>
<el-tooltip effect="dark"
content="请输入组件路径,如果是父组件填写 Layout 即可"
placement="right">
<i class="el-icon-info" style="margin-left: 10px;color:darkseagreen"></i>
</el-tooltip>
</el-form-item>
<el-form-item label="菜单图标">
<el-popover
placement="bottom-start"
:width="540"
v-model:visible="showChooseIcon"
trigger="click"
@show="showSelectIcon"
>
<icon-select ref="iconSelectRef" @selected="selected"/>
<template #reference>
<el-input v-model="formData.icon" placeholder="点击选择图标" readonly>
<template #prefix>
<svg-icon
v-if="formData.icon"
:icon-class="formData.icon"
class="el-input__icon"
style="height: 40px;width: 16px;"
color="#999"
/>
<i v-else class="el-icon-search el-input__icon"/>
</template>
</el-input>
</template>
</el-popover>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="formData.visible">
<el-radio :label="1">显示</el-radio>
<el-radio :label="0">隐藏</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="formData.sort" style="width: 100px" controls-position="right" :min="0"/>
</el-form-item>
<el-form-item label="跳转路径">
<el-input v-model="formData.redirect" placeholder="请输入跳转路径" maxlength="50"/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {listTableMenus, getMenuDetail, listTreeSelectMenus, addMenu, deleteMenus, updateMenu} from "@/api/system/menu";
import {Search, Plus, Edit, Refresh, Delete} from '@element-plus/icons'
import {ElForm, ElMessage, ElMessageBox} from "element-plus";
import {reactive, ref, unref, onMounted, toRefs} from "vue";
import SvgIcon from '@/components/SvgIcon/index.vue';
import TreeSelect from '@/components/TreeSelect/index.vue';
import IconSelect from '@/components/IconSelect/index.vue';
import {isExternal} from '@/utils/validate'
const emit = defineEmits(['menuClick'])
const iconSelectRef = ref(null);
const queryFormRef = ref(ElForm)
const dataFormRef = ref(ElForm)
const state = reactive({
loading: true,
// 选中ID数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
queryParams: {
pageNum: 1,
pageSize: 10,
name: undefined
},
menuList: [],
total: 0,
dialog: {
title: '',
visible: false
},
formData: {
id: undefined,
parentId: 0,
name: '',
visible: 1,
icon: '',
sort: 1,
component: 'Layout',
path: '',
redirect: ''
},
rules: {
parentId: [
{required: true, message: '请选择顶级菜单', trigger: 'blur'}
],
name: [
{required: true, message: '请输入菜单名称', trigger: 'blur'}
],
component: [
{required: true, message: '请输入页面路径', trigger: 'blur'}
]
},
menuOptions: [] as any[],
currentRow: undefined,
showChooseIcon: false,
isExternalPath: false
})
const {
loading,
single,
multiple,
queryParams,
menuList,
total,
dialog,
formData,
rules,
menuOptions,
isExternalPath,
showChooseIcon
} = toRefs(state)
function handleQuery() {
// 重置父组件
emit('menuClick', null)
state.loading = true
listTableMenus(state.queryParams).then(response => {
const {data, total} = response as any
state.menuList = data
state.total = total
state.loading = false
})
}
/**
* 加载菜单下拉树
*/
async function loadTreeSelectMenuOptions() {
const menuOptions: any[] = []
await listTreeSelectMenus().then(response => {
const menuOption = {id: 0, label: '顶级菜单', children: response.data}
menuOptions.push(menuOption)
state.menuOptions = menuOptions
})
}
/**
* 重置查询
*/
function resetQuery() {
const queryForm = unref(queryFormRef)
queryForm.resetFields()
handleQuery()
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: any) => item.id)
state.single = selection.length !== 1
state.multiple = !selection.length
}
function handleRowClick(row: any) {
state.currentRow = JSON.parse(JSON.stringify(row))
emit('menuClick', row)
}
async function handleAdd(row: any) {
await loadTreeSelectMenuOptions()
state.dialog = {
title: '添加菜单',
visible: true,
}
if (row.id) {
// 行点击新增
state.formData.parentId = row.id
if (row.id == 0) {
state.formData.component = 'Layout'
} else {
state.formData.component = ''
}
} else {
if (state.currentRow) {
// 工具栏新增
state.formData.parentId = (state.currentRow as any).id
state.formData.component = ''
} else {
state.formData.parentId = 0
state.formData.component = 'Layout'
}
}
}
async function handleUpdate(row: any) {
await loadTreeSelectMenuOptions()
state.dialog = {
title: '修改菜单',
visible: true
}
const id = row.id || state.ids
getMenuDetail(id).then(response => {
state.formData = response.data
const path = state.formData.path as string
state.isExternalPath = isExternal(path);
})
}
function submitForm() {
const dataForm = unref(dataFormRef)
dataForm.validate((valid: any) => {
if (valid) {
if (state.formData.id) {
updateMenu(state.formData.id, state.formData).then(response => {
ElMessage.success('修改成功')
state.dialog.visible = false
handleQuery()
})
} else {
addMenu(state.formData).then(response => {
ElMessage.success('新增成功')
state.dialog.visible = false
handleQuery()
})
}
}
})
}
function handleDelete(row: any) {
const ids = [row.id || state.ids].join(',')
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteMenus(ids).then(() => {
ElMessage.success('删除成功')
handleQuery()
})
}).catch(() =>
ElMessage.info('已取消删除')
)
}
/**
* 重置表单
*/
function resetForm() {
const dataForm = unref(dataFormRef)
dataForm.resetFields
}
function cancel() {
state.dialog.visible = false
resetForm()
}
function showSelectIcon() {
(iconSelectRef as any).value.reset();
state.showChooseIcon = true;
}
function selected(name: string) {
state.formData.icon = name;
state.showChooseIcon = false;
}
onMounted(() => {
handleQuery()
})
</script>
<style scoped>
</style>