refactor: 权限系统模块重构完成

Former-commit-id: b33078bfbf
This commit is contained in:
horizons 2022-10-21 00:59:49 +08:00
parent b0a0033371
commit cf24d080e5
16 changed files with 576 additions and 990 deletions

View File

@ -12,7 +12,7 @@ export function listDepartments(
queryParams?: DeptQueryParam
): AxiosPromise<DeptItem[]> {
return request({
url: '/youlai-system/api/v1/depts',
url: '/youlai-system/api/v1/dept',
method: 'get',
params: queryParams
});
@ -23,7 +23,7 @@ export function listDepartments(
*/
export function listDeptOptions(): AxiosPromise<Option[]> {
return request({
url: '/youlai-system/api/v1/depts/options',
url: '/youlai-system/api/v1/dept/options',
method: 'get'
});
}
@ -33,9 +33,9 @@ export function listDeptOptions(): AxiosPromise<Option[]> {
*
* @param id
*/
export function getDeptDetail(id: string): AxiosPromise<DeptFormData> {
export function getDeptForm(id: string): AxiosPromise<DeptFormData> {
return request({
url: '/youlai-system/api/v1/depts/' + id,
url: '/youlai-system/api/v1/dept/' + id + '/form',
method: 'get'
});
}
@ -47,7 +47,7 @@ export function getDeptDetail(id: string): AxiosPromise<DeptFormData> {
*/
export function addDept(data: DeptFormData) {
return request({
url: '/youlai-system/api/v1/depts',
url: '/youlai-system/api/v1/dept',
method: 'post',
data: data
});
@ -61,7 +61,7 @@ export function addDept(data: DeptFormData) {
*/
export function updateDept(id: string, data: DeptFormData) {
return request({
url: '/youlai-system/api/v1/depts/' + id,
url: '/youlai-system/api/v1/dept/' + id,
method: 'put',
data: data
});
@ -74,7 +74,7 @@ export function updateDept(id: string, data: DeptFormData) {
*/
export function deleteDept(ids: string) {
return request({
url: '/youlai-system/api/v1/depts/' + ids,
url: '/youlai-system/api/v1/dept/' + ids,
method: 'delete'
});
}

View File

@ -1,6 +1,6 @@
import { Option } from '@/types/common';
import {
DictFormTypeData,
DictTypeFormData,
DictItemFormData,
DictItemPageResult,
DictItemQueryParam,
@ -11,28 +11,28 @@ import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
*
*
*
* @param queryParams
*/
export function listPageDictTypes(
export function listDictTypePages(
queryParams: DictQueryParam
): AxiosPromise<DictPageResult> {
return request({
url: '/youlai-system/api/v1/dict-types',
url: '/youlai-system/api/v1/dict/types/pages',
method: 'get',
params: queryParams
});
}
/**
*
*
*
* @param id
*/
export function getDictFormData(id: number): AxiosPromise<DictFormTypeData> {
export function getDictTypeForm(id: number): AxiosPromise<DictTypeFormData> {
return request({
url: '/youlai-system/api/v1/dict-types/' + id + '/form_data',
url: '/youlai-system/api/v1/dict/types/' + id + '/form',
method: 'get'
});
}
@ -42,9 +42,9 @@ export function getDictFormData(id: number): AxiosPromise<DictFormTypeData> {
*
* @param data
*/
export function addDictType(data: DictFormTypeData) {
export function addDictType(data: DictTypeFormData) {
return request({
url: '/youlai-system/api/v1/dict-types',
url: '/youlai-system/api/v1/dict/types',
method: 'post',
data: data
});
@ -56,64 +56,59 @@ export function addDictType(data: DictFormTypeData) {
* @param id
* @param data
*/
export function updateDictType(id: number, data: DictFormTypeData) {
export function updateDictType(id: number, data: DictTypeFormData) {
return request({
url: '/youlai-system/api/v1/dict-types/' + id,
url: '/youlai-system/api/v1/dict/types/' + id,
method: 'put',
data: data
});
}
/**
*
*
* @param ids ID(,)
*
*/
export function deleteDictTypes(ids: string) {
return request({
url: '/youlai-system/api/v1/dict-types/' + ids,
url: '/youlai-system/api/v1/dict/types/' + ids,
method: 'delete'
});
}
/**
*
*
*
* @param queryParams
* @param typeCode
*/
export function listPageDictItems(
export function listDictItemsByTypeCode(
typeCode: string
): AxiosPromise<Option[]> {
return request({
url: '/youlai-system/api/v1/dict/types/' + typeCode + '/items',
method: 'get'
});
}
/**
*
*/
export function listDictItemPages(
queryParams: DictItemQueryParam
): AxiosPromise<DictItemPageResult> {
return request({
url: '/youlai-system/api/v1/dict-items',
url: '/youlai-system/api/v1/dict/items/pages',
method: 'get',
params: queryParams
});
}
/**
*
*
* @param typeCode
*/
export function getDictItemsByTypeCode(
typeCode: string
): AxiosPromise<Option[]> {
return request({
url: '/youlai-system/api/v1/dict-items/select_list',
method: 'get',
params: { typeCode: typeCode }
});
}
/**
*
*
*
* @param id
*/
export function getDictItemData(id: number): AxiosPromise<DictItemFormData> {
return request({
url: '/youlai-system/api/v1/dict-items/' + id + '/form_data',
url: '/youlai-system/api/v1/dict/items/' + id + '/form',
method: 'get'
});
}
@ -123,9 +118,9 @@ export function getDictItemData(id: number): AxiosPromise<DictItemFormData> {
*
* @param data
*/
export function addDictItem(data: DictItemFormData) {
export function saveDictItem(data: DictItemFormData) {
return request({
url: '/youlai-system/api/v1/dict-items',
url: '/youlai-system/api/v1/dict/items',
method: 'post',
data: data
});
@ -139,7 +134,7 @@ export function addDictItem(data: DictItemFormData) {
*/
export function updateDictItem(id: number, data: DictItemFormData) {
return request({
url: '/youlai-system/api/v1/dict-items/' + id,
url: '/youlai-system/api/v1/dict/items/' + id,
method: 'put',
data: data
});
@ -152,7 +147,7 @@ export function updateDictItem(id: number, data: DictItemFormData) {
*/
export function deleteDictItems(ids: string) {
return request({
url: '/youlai-system/api/v1/dict-items/' + ids,
url: '/youlai-system/api/v1/dict/items/' + ids,
method: 'delete'
});
}

View File

@ -21,7 +21,7 @@ import i18n from '@/lang/index';
import '@/styles/index.scss';
// 根据字典编码获取字典列表全局方法
import { getDictItemsByTypeCode } from '@/api/dict';
import { listDictItemsByTypeCode } from '@/api/dict';
const app = createApp(App);
@ -33,7 +33,7 @@ Object.keys(directive).forEach(key => {
});
// 全局方法
app.config.globalProperties.$getDictItemsByTypeCode = getDictItemsByTypeCode;
app.config.globalProperties.$listDictItemsByTypeCode = listDictItemsByTypeCode;
// 注册全局组件
app

View File

@ -28,7 +28,7 @@ router.beforeEach(async (to, from, next) => {
} else {
try {
await user.getUserInfo();
const roles = user.roles || ['ROOT'];
const roles = user.roles;
const accessRoutes: any = await permission.generateRoutes(roles);
accessRoutes.forEach((route: any) => {
router.addRoute(route);

View File

@ -74,7 +74,10 @@ const useUserStore = defineStore({
*
*/
logout() {
return new Promise((resolve, reject) => {
localStorage.remove('token');
this.RESET_STATE();
/* return new Promise((resolve, reject) => {
logout()
.then(() => {
localStorage.remove('token');
@ -85,7 +88,7 @@ const useUserStore = defineStore({
.catch(error => {
reject(error);
});
});
}); */
},
/**

View File

@ -2,7 +2,7 @@
*
*/
export interface DeptQueryParam {
name: string | undefined;
keywords: string | undefined;
status: number | undefined;
}

View File

@ -29,7 +29,7 @@ export type DictPageResult = PageResult<Dict[]>;
/**
*
*/
export interface DictFormTypeData {
export interface DictTypeFormData {
id: number | undefined;
name: string;
code: string;

View File

@ -10,7 +10,7 @@ import { onMounted, reactive, ref, toRefs } from 'vue';
// API
import {
getDeptDetail,
getDeptForm,
deleteDept,
updateDept,
addDept,
@ -131,7 +131,7 @@ async function handleUpdate(row: any) {
title: '修改部门',
visible: true
};
getDeptDetail(deptId).then((response: any) => {
getDeptForm(deptId).then((response: any) => {
state.formData = response.data;
});
}
@ -214,9 +214,9 @@ onMounted(() => {
</el-button>
</el-form-item>
<el-form-item prop="name">
<el-form-item prop="keywords">
<el-input
v-model="queryParams.name"
v-model="queryParams.keywords"
placeholder="请输入部门名称"
@keyup.enter="handleQuery"
/>
@ -254,15 +254,18 @@ onMounted(() => {
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="name" label="部门名称" />
<el-table-column prop="status" label="状态" width="100">
<el-table-column prop="name" label="部门名称" min-width="300" />
<el-table-column prop="status" label="状态" width="200">
<template #default="scope">
<el-tag v-if="scope.row.status == 1" type="success">正常</el-tag>
<el-tag v-else type="info">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="sort" label="显示排序" width="200" />
<el-table-column prop="sort" label="排序" width="200" />
<el-table-column prop="createTime" label="创建时间" width="250" />
<el-table-column prop="updateTime" label="修改时间" width="250" />
<el-table-column label="操作" align="center" width="150">
<template #default="scope">

View File

@ -15,9 +15,9 @@ import {
import { Dialog } from '@/types/common';
import {
listPageDictItems,
listDictItemPages,
getDictItemData,
addDictItem,
saveDictItem,
updateDictItem,
deleteDictItems
} from '@/api/dict';
@ -90,7 +90,7 @@ const {
function handleQuery() {
if (state.queryParams.typeCode) {
state.loading = true;
listPageDictItems(state.queryParams).then(({ data }) => {
listDictItemPages(state.queryParams).then(({ data }) => {
state.dictItemList = data.list;
state.total = data.total;
state.loading = false;
@ -146,7 +146,7 @@ function submitForm() {
handleQuery();
});
} else {
addDictItem(state.formData).then(() => {
saveDictItem(state.formData).then(() => {
ElMessage.success('新增成功');
cancel();
handleQuery();

View File

@ -47,9 +47,9 @@ export default {
border
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="字典名称" prop="name" width="120" />
<el-table-column label="字典名称" prop="name" />
<el-table-column label="字典编码" prop="code" />
<el-table-column label="状态" align="center" width="80">
<el-table-column label="状态" align="center" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">启用</el-tag>
<el-tag v-else type="info">禁用</el-tag>
@ -131,8 +131,8 @@ export default {
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs } from 'vue';
import {
listPageDictTypes,
getDictFormData,
listDictTypePages,
getDictTypeForm,
addDictType,
updateDictType,
deleteDictTypes
@ -141,7 +141,7 @@ import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import { Dialog } from '@/types/common';
import { Dict, DictFormTypeData, DictQueryParam } from '@/types/api/dict';
import { Dict, DictTypeFormData, DictQueryParam } from '@/types/api/dict';
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
@ -165,7 +165,7 @@ const state = reactive({
dialog: { visible: false } as Dialog,
formData: {
status: 1
} as DictFormTypeData,
} as DictTypeFormData,
rules: {
name: [{ required: true, message: '请输入字典名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入字典编码', trigger: 'blur' }]
@ -178,7 +178,7 @@ const { total, dialog, loading, dictList, formData, rules, queryParams } =
function handleQuery() {
emit('dictClick', null);
state.loading = true;
listPageDictTypes(state.queryParams).then(({ data }) => {
listDictTypePages(state.queryParams).then(({ data }) => {
state.dictList = data.list;
state.total = data.total;
state.loading = false;
@ -209,7 +209,7 @@ function handleUpdate(row: any) {
visible: true
};
const id = row.id || state.ids;
getDictFormData(id).then(({ data }) => {
getDictTypeForm(id).then(({ data }) => {
state.formData = data;
});
}

View File

@ -7,7 +7,7 @@ import { reactive, toRefs } from 'vue';
const state = reactive({
typeCode: '',
typeName: '',
typeName: ''
});
const { typeCode, typeName } = toRefs(state);
@ -26,7 +26,7 @@ const handleDictTypeClick = (row: any) => {
<template>
<div class="app-container">
<el-row :gutter="10">
<el-col :span="12" :xs="24">
<el-col :span="8" :xs="24">
<el-card class="box-card">
<template #header>
<svg-icon icon-class="dict" />
@ -36,7 +36,7 @@ const handleDictTypeClick = (row: any) => {
</el-card>
</el-col>
<el-col :span="12" :xs="24">
<el-col :span="16" :xs="24">
<el-card class="box-card">
<template #header>
<svg-icon icon-class="dict_item" />

View File

@ -1,465 +0,0 @@
<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="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"
highlight-current-row
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
@row-click="handleRowClick"
row-key="id"
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="100">
<template #default="scope">
<el-tag v-if="scope.row.type === 'MENU'" type="success">菜单</el-tag>
<el-tag v-if="scope.row.type === 'CATALOG'" type="warning"
>目录</el-tag
>
<el-tag v-if="scope.row.type === 'EXTLINK'" type="info">外链</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" width="100">
<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="100" prop="sort">
</el-table-column>
<el-table-column label="操作" align="center" width="200">
<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"
@close="cancel"
width="750px"
>
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="父级菜单" prop="parentId">
<el-tree-select
v-model="formData.parentId"
placeholder="选择上级菜单"
:data="menuOptions"
filterable
check-strictly
:render-after-expand="false"
/>
</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="formData.type"
@change="handleMenuTypeChange"
>
<el-radio label="MENU">菜单</el-radio>
<el-radio label="CATALOG">目录</el-radio>
<el-radio label="EXTLINK">外链</el-radio>
</el-radio-group>
</el-form-item>
<!-- 路由路径(浏览器地址栏显示) -->
<el-form-item
v-if="formData.type == 'EXTLINK'"
label="外链地址"
prop="path"
>
<el-input v-model="formData.path" placeholder="请输入外链完整路径" />
</el-form-item>
<el-form-item v-else label="路由路径" prop="path">
<el-input
v-if="formData.type == 'CATALOG'"
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 == 'MENU'"
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-form-item>
<el-form-item label="图标" prop="icon">
<el-popover
ref="popoverRef"
placement="bottom-start"
:width="570"
trigger="click"
>
<template #reference>
<el-input
v-model="formData.icon"
placeholder="点击选择图标"
readonly
@click="iconSelectVisible = true"
>
<template #prefix>
<svg-icon :icon-class="formData.icon" color="999" />
</template>
</el-input>
</template>
<icon-select @selected="selected" />
</el-popover>
</el-form-item>
<el-form-item label="跳转路由">
<el-input
v-model="formData.redirect"
placeholder="跳转路由路径"
maxlength="50"
/>
</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>
<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 { reactive, ref, onMounted, toRefs } from 'vue';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import { ElForm, ElMessage, ElMessageBox, ElPopover } from 'element-plus';
import { Dialog, Option } from '@/types/common';
import { MenuFormData, MenuItem, MenuQueryParam } from '@/types/api/menu';
// API
import {
listMenus,
getMenuDetail,
listMenuOptions,
addMenu,
deleteMenus,
updateMenu
} from '@/api/menu';
import SvgIcon from '@/components/SvgIcon/index.vue';
import IconSelect from '@/components/IconSelect/index.vue';
const emit = defineEmits(['menuClick']);
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const popoverRef = ref(ElPopover);
const state = reactive({
loading: true,
// ID
ids: [],
//
single: true,
//
multiple: true,
queryParams: {} as MenuQueryParam,
menuList: [] as MenuItem[],
dialog: { visible: false } as Dialog,
formData: {
parentId: '0',
name: '',
visible: 1,
sort: 1,
component: undefined,
type: 'MENU'
} as MenuFormData,
rules: {
parentId: [{ required: true, message: '请选择顶级菜单', trigger: 'blur' }],
name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
type: [{ required: true, message: '请选择菜单类型', trigger: 'blur' }],
path: [{ required: true, message: '请输入路由路径', trigger: 'blur' }],
component: [
{ required: true, message: '请输入组件完整路径', trigger: 'blur' }
]
},
menuOptions: [] as Option[],
currentRow: undefined,
// Icon
iconSelectVisible: false,
cacheData: {
menuType: '',
menuPath: ''
}
});
const {
loading,
queryParams,
menuList,
dialog,
formData,
rules,
menuOptions,
iconSelectVisible,
cacheData
} = toRefs(state);
/**
* 查询
*/
function handleQuery() {
//
emit('menuClick', null);
state.loading = true;
listMenus(state.queryParams).then(({ data }) => {
state.menuList = data;
state.loading = false;
});
}
/**
* 加载菜单下拉树
*/
async function loadMenuData() {
const menuOptions: any[] = [];
await listMenuOptions().then(({ data }) => {
const menuOption = { value: '0', label: '顶级菜单', children: data };
menuOptions.push(menuOption);
state.menuOptions = menuOptions;
});
}
/**
* 重置查询
*/
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleRowClick(row: any) {
state.currentRow = JSON.parse(JSON.stringify(row));
emit('menuClick', row);
}
/**
* 新增菜单
* @param row
*/
async function handleAdd(row: any) {
formData.value.id = undefined;
await loadMenuData();
state.dialog = {
title: '添加菜单',
visible: true
};
if (row.id) {
//
state.formData.parentId = row.id;
if (row.id == '0') {
state.formData.type = 'CATALOG';
} else {
state.formData.type = 'MENU';
}
} else {
//
if (state.currentRow) {
state.formData.parentId = (state.currentRow as any).id;
state.formData.type = 'MENU';
} else {
state.formData.parentId = '0';
state.formData.type = 'CATALOG';
}
}
}
/**
* 编辑菜单
*/
async function handleUpdate(row: MenuFormData) {
await loadMenuData();
state.dialog = {
title: '编辑菜单',
visible: true
};
const id = row.id as string;
getMenuDetail(id).then(({ data }) => {
state.formData = data;
cacheData.value.menuType = data.type;
cacheData.value.menuPath = data.path;
});
}
/**
* 菜单类型 change
*/
function handleMenuTypeChange(menuType: any) {
if (menuType !== cacheData.value.menuType) {
formData.value.path = '';
} else {
formData.value.path = cacheData.value.menuPath;
}
}
/**
* 菜单提交
*/
function submitForm() {
dataFormRef.value.validate((isValid: boolean) => {
if (isValid) {
if (state.formData.id) {
updateMenu(state.formData.id, state.formData).then(() => {
ElMessage.success('修改成功');
cancel();
handleQuery();
});
} else {
addMenu(state.formData).then(() => {
ElMessage.success('新增成功');
cancel();
handleQuery();
});
}
}
});
}
/**
* 删除菜单
*
* @param row
*/
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 cancel() {
dataFormRef.value.resetFields();
state.dialog.visible = false;
}
/**
* 选择图标后事件
*/
function selected(name: string) {
state.formData.icon = name;
state.iconSelectVisible = false;
}
onMounted(() => {
handleQuery();
});
</script>

View File

@ -1,403 +0,0 @@
<script setup lang="ts">
import {
onMounted,
watch,
reactive,
ref,
getCurrentInstance,
toRefs
} from 'vue';
import {
listPermPages,
getPermFormDetail,
addPerm,
updatePerm,
deletePerms
} from '@/api/perm';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import { Dialog, Option } from '@/types/common';
import { PermFormData, PermItem, PermQueryParam } from '@/types/api/perm';
import { MenuItem } from '@/types/api/menu';
const { proxy }: any = getCurrentInstance();
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const props = defineProps({
menu: {
type: Object,
default: () => {
return {} as MenuItem;
}
}
});
watch(
() => props.menu,
value => {
queryParams.value.menuId = value.id;
console.log('menu', value);
handleQuery();
},
{
deep: true
}
);
const state = reactive({
loading: true,
// ID
ids: [],
//
single: true,
//
multiple: true,
queryParams: {
pageNum: 1,
pageSize: 10
} as PermQueryParam,
permList: [] as PermItem[],
total: 0,
dialog: {
visible: false
} as Dialog,
formData: {} as PermFormData,
rules: {
name: [{ required: true, message: '请输入权限名称', trigger: 'blur' }],
perm: [{ required: true, message: '请输入权限标识', trigger: 'blur' }],
method: [{ required: true, message: '请选择请求方式', trigger: 'blur' }]
},
microServiceOptions: [] as Option[],
requestMethodOptions: [] as Option[],
urlPerm: {
requestMethod: '',
serviceName: '',
requestPath: ''
}
});
const {
loading,
multiple,
permList,
total,
dialog,
formData,
rules,
microServiceOptions,
requestMethodOptions,
urlPerm,
queryParams
} = toRefs(state);
function handleQuery() {
if (state.queryParams.menuId) {
state.loading = true;
listPermPages(state.queryParams).then(({ data }) => {
state.permList = data.list;
state.total = data.total;
state.loading = false;
});
} else {
state.loading = false;
state.permList = [];
state.total = 0;
state.queryParams.pageNum = 1;
}
}
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: any) => item.id);
state.single = selection.length !== 1;
state.multiple = !selection.length;
}
/**
* 加载字典数据
*/
function loadDictOptions() {
proxy.$getDictItemsByTypeCode('micro_service').then((response: any) => {
state.microServiceOptions = response.data;
});
proxy.$getDictItemsByTypeCode('request_method').then((response: any) => {
state.requestMethodOptions = response.data;
});
}
function handleAdd() {
loadDictOptions();
state.dialog = {
title: '添加权限',
visible: true
};
}
function handleUpdate(row: any) {
loadDictOptions();
state.dialog = {
title: '修改权限',
visible: true
};
const id = row.id || state.ids;
getPermFormDetail(id).then(response => {
const { data } = response;
state.formData = data;
if (data && data.urlPerm) {
// GET:/youlai-system/api/v1/users
const urlPermArr = data.urlPerm.split(':');
state.urlPerm.requestMethod = urlPermArr[0];
state.urlPerm.serviceName = urlPermArr[1].substring(
1,
urlPermArr[1].substring(1).indexOf('/') + 1
);
state.urlPerm.requestPath = urlPermArr[1].substring(
urlPermArr[1].substring(1).indexOf('/') + 1
);
}
});
}
function submitForm() {
dataFormRef.value.validate((isValid: any) => {
if (isValid) {
//
console.log(state.urlPerm.requestPath, state.formData.btnPerm);
if (!(state.urlPerm.requestPath || state.formData.btnPerm)) {
ElMessage.warning('请至少填写一种权限');
return false;
}
// URL
if (!state.urlPerm.requestPath) {
if (!state.urlPerm.requestMethod) {
ElMessage.warning('URL权限的请求方式不能为空');
return false;
}
if (!state.urlPerm.serviceName) {
ElMessage.warning('URL权限的所属服务不能为空');
return false;
}
state.formData.urlPerm =
state.urlPerm.requestMethod +
':/' +
state.urlPerm.serviceName +
state.urlPerm.requestPath;
}
formData.value.menuId = props.menu.id;
if (state.formData.id) {
updatePerm(state.formData.id, state.formData).then(() => {
ElMessage.success('修改成功');
cancel();
handleQuery();
});
} else {
addPerm(state.formData).then(() => {
ElMessage.success('新增成功');
cancel();
handleQuery();
});
}
}
});
}
/**
* 重置表单
*/
function resetForm() {
dataFormRef.value.resetFields();
state.urlPerm = {
requestMethod: '',
serviceName: '',
requestPath: ''
};
}
function cancel() {
resetForm();
state.dialog.visible = false;
}
function handleDelete(row: any) {
const ids = [row.id || state.ids].join(',');
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
deletePerms(ids).then(() => {
ElMessage.success('删除成功');
handleQuery();
});
})
.catch(() => ElMessage.info('已取消删除'));
}
onMounted(() => {
handleQuery();
});
</script>
<template>
<div claas="component-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button
type="success"
:icon="Plus"
v-if="menu.id && menu.type == 'MENU'"
@click="handleAdd"
>新增</el-button
>
<el-button
type="danger"
:icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-if="menu.id && menu.type == 'MENU'"
>删除</el-button
>
</el-form-item>
<el-form-item prop="name">
<el-input
v-model="queryParams.name"
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="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table
:data="permList"
v-loading="loading"
@selection-change="handleSelectionChange"
border
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column label="权限名称" prop="name" width="150" />
<el-table-column label="URL权限" align="center">
<el-table-column prop="serviceName" label="所属服务" />
<el-table-column prop="requestMethod" label="请求方式" />
<el-table-column prop="requestPath" label="请求路径" />
</el-table-column>
<el-table-column label="按钮权限" prop="btnPerm" width="200" />
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button
type="primary"
:icon="Edit"
circle
plain
@click="handleUpdate(scope.row)"
/>
<el-button
type="danger"
:icon="Delete"
circle
plain
@click="handleDelete(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination
v-if="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery"
/>
<!-- 表单弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="700px">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="120px"
>
<el-form-item label="权限名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入权限名称" />
</el-form-item>
<el-form-item label="URL权限标识" prop="urlPerm">
<el-input placeholder="/api/v1/users" v-model="urlPerm.requestPath">
<template #prepend>
<el-select
v-model="urlPerm.serviceName"
style="width: 130px"
placeholder="所属服务"
clearable
>
<el-option
v-for="item in microServiceOptions"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</el-select>
<el-select
v-model="urlPerm.requestMethod"
style="width: 120px; margin-left: 20px"
placeholder="请求方式"
clearable
>
<el-option
v-for="item in requestMethodOptions"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</el-select>
</template>
</el-input>
<el-link v-show="urlPerm.requestMethod">
{{ urlPerm.requestMethod }}:/{{ urlPerm.serviceName
}}{{ urlPerm.requestPath }}
</el-link>
</el-form-item>
<el-form-item label="按钮权限标识" prop="btnPerm">
<el-input v-model="formData.btnPerm" placeholder="sys:user:add" />
</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>
<style lang="scss" scoped>
.component-container {
margin-bottom: 20px;
}
</style>

View File

@ -1,59 +1,499 @@
<template>
<div class="app-container">
<el-row :gutter="10">
<el-col :span="10" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="menu" />
菜单列表
</template>
<menu-table @menuClick="handleMenuClick" />
</el-card>
</el-col>
<el-col :span="14" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="perm" />
<span style="margin: 0 5px">权限列表</span>
<el-tag type="success" v-if="menu.id" size="small">{{
menu.name
}}</el-tag>
<el-link :underline="false" type="warning" v-else size="small"
><el-icon><WarningFilled /></el-icon></el-link
<!-- 搜索表单 -->
<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="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"
highlight-current-row
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
@row-click="handleRowClick"
row-key="id"
border
default-expand-all
>
<el-table-column label="菜单名称">
<template #default="scope">
<svg-icon
:icon-class="
scope.row.type === 'BUTTON' ? 'button' : scope.row.icon
"
/>
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="菜单类型" align="center" width="150">
<template #default="scope">
<el-tag v-if="scope.row.type === 'CATALOG'" type="warning"
>目录</el-tag
>
<el-tag v-if="scope.row.type === 'MENU'" type="success">菜单</el-tag>
<el-tag v-if="scope.row.type === 'BUTTON'" type="danger">按钮</el-tag>
<el-tag v-if="scope.row.type === 'EXTLINK'" type="info">外链</el-tag>
</template>
</el-table-column>
<el-table-column
label="权限标识"
align="center"
width="200"
prop="perm"
/>
<el-table-column label="状态" align="center" width="150">
<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="100" prop="sort" />
<el-table-column
label="创建时间"
align="center"
width="200"
prop="createTime"
>
</el-table-column>
<el-table-column
label="修改时间"
align="center"
width="200"
prop="updateTime"
>
</el-table-column>
<el-table-column label="操作" align="center" width="200">
<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>
<!-- dialog -->
<el-dialog
:title="dialog.title"
v-model="dialog.visible"
@close="cancel"
width="750px"
>
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="父级菜单" prop="parentId">
<el-tree-select
v-model="formData.parentId"
placeholder="选择上级菜单"
:data="menuOptions"
filterable
check-strictly
:render-after-expand="false"
/>
</el-form-item>
<el-form-item label="菜单名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入菜单名称" />
</el-form-item>
<el-form-item label="菜单类型" prop="type">
<el-radio-group
v-model="formData.type"
@change="handleMenuTypeChange"
>
<el-radio label="CATALOG">目录</el-radio>
<el-radio label="MENU">菜单</el-radio>
<el-radio label="BUTTON">按钮</el-radio>
<el-radio label="EXTLINK">外链</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="formData.type == 'EXTLINK'"
label="外链地址"
prop="path"
>
<el-input v-model="formData.path" placeholder="请输入外链完整路径" />
</el-form-item>
<el-form-item
label="路由路径"
prop="path"
v-if="formData.type == 'CATALOG' || formData.type == 'MENU'"
>
<el-input
v-if="formData.type == 'CATALOG'"
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 == 'MENU'"
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>
<perm-table :menu="menu" />
</el-card>
</el-col>
</el-row>
<template v-if="formData.parentId != '0'" #append>.vue</template>
</el-input>
</el-form-item>
<!-- 权限标识 -->
<el-form-item
v-if="formData.type == 'BUTTON'"
label="权限标识"
prop="perm"
>
<el-input v-model="formData.perm" placeholder="sys:user:add" />
</el-form-item>
<el-form-item
label="图标"
prop="icon"
v-if="formData.type !== 'BUTTON'"
>
<el-popover
ref="popoverRef"
placement="bottom-start"
:width="570"
trigger="click"
>
<template #reference>
<el-input
v-model="formData.icon"
placeholder="点击选择图标"
readonly
@click="iconSelectVisible = true"
>
<template #prefix>
<svg-icon :icon-class="formData.icon" />
</template>
</el-input>
</template>
<icon-select @selected="selected" />
</el-popover>
</el-form-item>
<el-form-item label="跳转路由" v-if="formData.type == 'CATEGORY'">
<el-input v-model="formData.redirect" placeholder="跳转路由" />
</el-form-item>
<el-form-item label="状态" v-if="formData.type !== 'BUTTON'">
<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>
<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 SvgIcon from '@/components/SvgIcon/index.vue';
import MenuTable from './components/Menu.vue';
import PermTable from './components/Perm.vue';
import { reactive, ref, onMounted, toRefs } from 'vue';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import { ElForm, ElMessage, ElMessageBox, ElPopover } from 'element-plus';
import { Dialog, Option } from '@/types/common';
import { MenuFormData, MenuItem, MenuQueryParam } from '@/types/api/menu';
// API
import {
listMenus,
getMenuDetail,
listMenuOptions,
addMenu,
deleteMenus,
updateMenu
} from '@/api/menu';
import SvgIcon from '@/components/SvgIcon/index.vue';
import IconSelect from '@/components/IconSelect/index.vue';
const emit = defineEmits(['menuClick']);
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const popoverRef = ref(ElPopover);
import { reactive, toRefs } from 'vue';
import { WarningFilled } from '@element-plus/icons-vue';
import { MenuItem } from '@/types/api/menu';
const state = reactive({
menu: {} as MenuItem
loading: true,
// ID
ids: [],
//
single: true,
//
multiple: true,
queryParams: {} as MenuQueryParam,
menuList: [] as MenuItem[],
dialog: { visible: false } as Dialog,
formData: {
parentId: '0',
name: '',
visible: 1,
sort: 1,
component: undefined,
type: 'MENU'
} as MenuFormData,
rules: {
parentId: [{ required: true, message: '请选择顶级菜单', trigger: 'blur' }],
name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
type: [{ required: true, message: '请选择菜单类型', trigger: 'blur' }],
path: [{ required: true, message: '请输入路由路径', trigger: 'blur' }],
component: [
{ required: true, message: '请输入组件完整路径', trigger: 'blur' }
]
},
menuOptions: [] as Option[],
currentRow: undefined,
// Icon
iconSelectVisible: false,
cacheData: {
menuType: '',
menuPath: ''
}
});
const { menu } = toRefs(state);
const {
loading,
queryParams,
menuList,
dialog,
formData,
rules,
menuOptions,
iconSelectVisible,
cacheData
} = toRefs(state);
function handleMenuClick(menuRow: MenuItem) {
if (menuRow) {
menu.value.id = menuRow.id;
menu.value.type = menuRow.type;
menu.value.name = menuRow.name;
/**
* 查询
*/
function handleQuery() {
//
emit('menuClick', null);
state.loading = true;
listMenus(state.queryParams).then(({ data }) => {
state.menuList = data;
state.loading = false;
});
}
/**
* 加载菜单下拉树
*/
async function loadMenuData() {
const menuOptions: any[] = [];
await listMenuOptions().then(({ data }) => {
const menuOption = { value: '0', label: '顶级菜单', children: data };
menuOptions.push(menuOption);
state.menuOptions = menuOptions;
});
}
/**
* 重置查询
*/
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleRowClick(row: any) {
state.currentRow = JSON.parse(JSON.stringify(row));
emit('menuClick', row);
}
/**
* 新增菜单打开
*/
async function handleAdd(row: any) {
formData.value.id = undefined;
await loadMenuData();
dialog.value = {
title: '添加菜单',
visible: true
};
if (row.id) {
//
formData.value.parentId = row.id;
} else {
menu.value.id = undefined;
menu.value.type = undefined;
menu.value.name = '';
//
if (state.currentRow) {
//
formData.value.parentId = (state.currentRow as any).id;
} else {
//
formData.value.parentId = '0';
}
}
}
</script>
<style scoped></style>
/**
* 编辑菜单
*/
async function handleUpdate(row: MenuFormData) {
await loadMenuData();
state.dialog = {
title: '编辑菜单',
visible: true
};
const id = row.id as string;
getMenuDetail(id).then(({ data }) => {
state.formData = data;
cacheData.value.menuType = data.type;
cacheData.value.menuPath = data.path;
});
}
/**
* 菜单类型 change
*/
function handleMenuTypeChange(menuType: any) {
if (menuType !== cacheData.value.menuType) {
formData.value.path = '';
} else {
formData.value.path = cacheData.value.menuPath;
}
}
/**
* 菜单提交
*/
function submitForm() {
dataFormRef.value.validate((isValid: boolean) => {
if (isValid) {
if (state.formData.id) {
updateMenu(state.formData.id, state.formData).then(() => {
ElMessage.success('修改成功');
cancel();
handleQuery();
});
} else {
addMenu(state.formData).then(() => {
ElMessage.success('新增成功');
cancel();
handleQuery();
});
}
}
});
}
/**
* 删除菜单
*
* @param row
*/
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 cancel() {
dataFormRef.value.resetFields();
state.dialog.visible = false;
}
/**
* 选择图标后事件
*/
function selected(name: string) {
state.formData.icon = name;
state.iconSelectVisible = false;
}
onMounted(() => {
handleQuery();
});
</script>

View File

@ -332,8 +332,20 @@ onMounted(() => {
border
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色名称" prop="name" />
<el-table-column label="角色编码" prop="code" />
<el-table-column label="角色名称" prop="name" min-width="300" />
<el-table-column label="角色编码" prop="code" width="200" />
<el-table-column label="状态" align="center" width="150">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">正常</el-tag>
<el-tag v-else type="info">禁用</el-tag>
</template>
</el-table-column>
<el-table-column label="排序" align="center" width="100" prop="sort" />
<el-table-column prop="createTime" label="创建时间" width="250" />
<el-table-column prop="updateTime" label="修改时间" width="250" />
<el-table-column label="操作" align="center" width="200">
<template #default="scope">
<el-tooltip content="分配资源" effect="light">

View File

@ -367,7 +367,7 @@ async function loadDeptOptions() {
* 加载性别字典
*/
function loadGenderOptions() {
proxy.$getDictItemsByTypeCode('gender').then((response: any) => {
proxy.$listDictItemsByTypeCode('gender').then((response: any) => {
state.genderOptions = response?.data;
});
}
@ -607,6 +607,7 @@ onMounted(() => {
label="用户编号"
align="center"
prop="id"
width="100"
/>
<el-table-column
key="username"