Java道经 - 项目 - MyLesson - 后台前端(二)
文章目录
S03. UMS用户服务
武技:在 router/index.js 文件中开发全部相关页面路由配置
import User from "../views/ums/user/User.vue";
import UserInsert from "../views/ums/user/UserInsert.vue";
import UserUpdate from "../views/ums/user/UserUpdate.vue";
import UserUpdateRoles from "../views/ums/user/UserUpdateRoles.vue";
import Role from "../views/ums/role/Role.vue";
import RoleInsert from "../views/ums/role/RoleInsert.vue";
import RoleUpdate from "../views/ums/role/RoleUpdate.vue";
import RoleUpdateMenus from "../views/ums/role/RoleUpdateMenus.vue";
import Menu from "../views/ums/menu/Menu.vue";
import MenuInsert from "../views/ums/menu/MenuInsert.vue";
import MenuUpdate from "../views/ums/menu/MenuUpdate.vue";
import SubMenu from "../views/ums/menu/sub/SubMenu.vue";
import SubMenuInsert from "../views/ums/menu/sub/SubMenuInsert.vue";
import SubMenuUpdate from "../views/ums/menu/sub/SubMenuUpdate.vue";
const router = createRouter({
history: createWebHashHistory(),
routes: [
{path: '/', name: 'Login', component: Login},
{
path: '/Main', name: 'Main', component: Main,
redirect: '/Dashboard',
children: [
...
{path: '/User', name: 'User', component: User},
{path: '/UserInsert', name: 'UserInsert', component: UserInsert},
{path: '/UserUpdate', name: 'UserUpdate', component: UserUpdate},
{path: '/UserUpdateRoles', name: 'UserUpdateRoles', component: UserUpdateRoles},
{path: '/Role', name: 'Role', component: Role},
{path: '/RoleInsert', name: 'RoleInsert', component: RoleInsert},
{path: '/RoleUpdate', name: 'RoleUpdate', component: RoleUpdate},
{path: '/RoleUpdateMenus', name: 'RoleUpdateMenus', component: RoleUpdateMenus},
{path: '/Menu', name: 'Menu', component: Menu},
{path: '/MenuInsert', name: 'MenuInsert', component: MenuInsert},
{path: '/MenuUpdate', name: 'MenuUpdate', component: MenuUpdate},
{path: '/SubMenu', name: 'SubMenu', component: SubMenu},
{path: '/SubMenuInsert', name: 'SubMenuInsert', component: SubMenuInsert},
{path: '/SubMenuUpdate', name: 'SubMenuUpdate', component: SubMenuUpdate},
]
}
]
});
E01. 用户管理模块
1. 查看用户列表
心法:查看用户列表页面
武技:开发查看用户列表 views/ums/user/User.vue
<script setup>
import MyNav from "../../../components/MyNav.vue";
import MyHead from "../../../components/MyHead.vue";
import MyTable from "../../../components/MyTable.vue";
import {onMounted, reactive, ref} from "vue";
import {deleteApi, deleteBatchApi, excelApi, pageApi} from "../../../api/index.js";
import {MINIO_AVATAR} from "../../../const/index.js";
import {getResponseData, myPage} from "../../../request/index.js";
import {isNotEmpty, isNotNull} from "../../../util/index.js";
import {genderFormat} from "../../../util/index.js";
import {resetPasswordApi} from "../../../api/ums/user.js";
import {ElMessage, ElMessageBox} from "element-plus";
import router from "../../../router";
// 路径导航
const navItems = [
{icon: 'User', label: '用户管理'},
{icon: 'User', label: '用户列表'},
];
// 数据头
const headItems = [
{type: 'ipt', span: 5, placeholder: '按账号搜索', callback: pageByUsername},
{type: 'ipt', span: 5, placeholder: '按昵称搜索', callback: pageByNickname},
{type: 'ipt', span: 5, placeholder: '按手机搜索', callback: pageByPhone},
]
// 表格列
const columns = [
{label: '头像', prop: 'avatar', type: 'img', minio: MINIO_AVATAR},
{label: '性别', prop: 'gender', type: 'tag', format: genderFormat, width: 80, tagTypeFn: e => e === 0 ? 'danger' : e === 1 ? 'primary' : 'warning'},
{label: '昵称', prop: 'nickname'},
{label: '账号', prop: 'username'},
{label: '手机', prop: 'phone', width: 100},
{label: '邮件', prop: 'email'},
{label: '姓名', prop: 'realname', width: 80},
{label: '身份证号', prop: 'idcard'},
{label: '年龄', prop: 'age', width: 80},
{label: '星座', prop: 'zodiac', type: 'tag', width: 80},
{label: '籍贯', prop: 'province', type: 'tag', width: 100},
{label: '描述', prop: 'info', type: 'card'},
];
// 按钮列
const buttons = [
{label: '重设角色', type: 'success', callback: toUserUpdateRoles},
{label: '重置密码', type: 'danger', callback: resetPassword}
];
/* ==================== 分页查询 ==================== */
// 表格数据 + 分页数据
let records = ref();
let pageInfo = reactive({pageNum: 1, pageSize: 5, total: 0, callback: page});
// 分页查询条件字段:账号,昵称,手机
let username = ref();
let nickname = ref();
let phone = ref();
/**
* 分页查询记录
*
* 1. 定义分页基础配置,包括 records, pageInfo, api, params 等。
* 2. 附加分页查询条件,如账号,昵称,手机号码等。
* 3. 异步发送分页查询请求。
*
* @param pageNum 当前第几页,默认 1
* @param pageSize 每页多少条,默认 5
*/
async function page(pageNum = pageInfo['pageNum'], pageSize = pageInfo['pageSize']) {
// 分页基础配置
let config = {
api: pageApi,
args: {module: 'user'},
params: {pageNum, pageSize},
records, pageInfo,
}
// 附加为分页条件
if (isNotEmpty(username.value)) config['params']['username'] = username.value;
if (isNotEmpty(nickname.value)) config['params']['nickname'] = nickname.value;
if (isNotEmpty(phone.value)) config['params']['phone'] = phone.value;
// 发送分页请求
await myPage(config);
}
/* ==================== 按登录账号模糊查询 ==================== */
/**
* 若输入框有值,或者当前正处于按条件分页状态时,进行操作:
*
* <p> 1. 将输入框中的值赋值给分页条件字段变量。
* <p> 2. 重新发送分页请求。
*
* @param val 输入框中的值
*/
function pageByUsername(val) {
// 仅当输入框有值,或者当前处于按条件分页状态时,发送分页请求
if (isNotNull(val) || username.value) {
username.value = val;
page();
}
}
/* ==================== 按用户昵称模糊查询 ==================== */
/**
* 若输入框有值,或者当前正处于按条件分页状态时,进行操作:
*
* <p> 1. 将输入框中的值赋值给分页条件字段变量。
* <p> 2. 重新发送分页请求。
*
* @param val 输入框中的值
*/
function pageByNickname(val) {
// 仅当输入框有值,或者当前处于按条件分页状态时,发送分页请求
if (isNotNull(val) || nickname.value) {
nickname.value = val;
page();
}
}
/* ==================== 按手机号码模糊查询 ==================== */
/**
* 若输入框有值,或者当前正处于按条件分页状态时,进行操作:
*
* <p> 1. 将输入框中的值赋值给分页条件字段变量。
* <p> 2. 重新发送分页请求。
*
* @param val 输入框中的值
*/
function pageByPhone(val) {
// 仅当输入框有值,或者当前处于按条件分页状态时,发送分页请求
if (isNotNull(val) || phone.value) {
phone.value = val;
page();
}
}
/* ==================== 重置密码 ==================== */
async function resetPassword(row) {
ElMessageBox.confirm('确认重置密码吗?').then(() => {
resetPasswordApi(row['id']).then(res => {
if (isNotNull(getResponseData(res))) {
ElMessage.success('密码重置为 123456789');
}
}
).catch(() => {
ElMessage.info('已取消');
});
});
}
/* ==================== 重设角色 ==================== */
function toUserUpdateRoles(row) {
sessionStorage.setItem('userId', row['id']);
sessionStorage.setItem('nickname', row['nickname'].toString());
router.push('/UserUpdateRoles')
}
/* ==================== 报表打印 ==================== */
function downloadExcel() {
excelApi('/user/excel', '用户报表');
}
/* ==================== 删除成功回调 ==================== */
function deleteSuccess() {
ElMessage.success('删除成功');
page();
}
/* ==================== 加载函数 ==================== */
onMounted(() => page());
</script>
<template v-if="records">
<my-nav :items="navItems"/>
<my-head :items="headItems"/>
<my-table module="user"
insert-page="/UserInsert"
update-page="/UserUpdate"
:records="records"
:columns="columns"
:buttons="buttons"
:delete-api="deleteApi"
:delete-batch-api="deleteBatchApi"
:delete-callback="deleteSuccess"
:excel-api="downloadExcel"
:page-info="pageInfo"/>
</template>
<style scoped lang="scss"></style>
2. 添加用户记录
心法:添加用户记录页面
武技:开发添加用户页面 views/ums/user/UserInsert.vue
<script setup>
import MyNav from "../../../components/MyNav.vue";
import MyForm from "../../../components/MyForm.vue";
import {reactive, ref} from "vue";
import {insertApi} from "../../../api/index.js";
import {RULE} from "../../../const/index.js";
import router from "../../../router/index.js";
import {ElMessage} from "element-plus";
// 路径导航
const navItems = [
{icon: 'User', label: '用户管理'},
{icon: 'User', label: '用户列表', url: '/User'},
{icon: 'Plus', label: '添加用户'},
];
/* ==================== 添加表单 ==================== */
// 表单项 + 表单值 + 表单规则
let items = ref([
{label: '账号', prop: 'username', required: true, span: 12},
{label: '密码', prop: 'password', required: true, type: "password", span: 12},
{label: '姓名', prop: 'realname', required: true, span: 12},
{label: '邮箱', prop: 'email', required: true, span: 12},
{label: '手机号码', prop: 'phone', required: true, span: 12},
{label: '身份证号', prop: 'idcard', required: true, span: 12},
{label: '描述', prop: 'info', required: true, type: 'textarea'},
]);
let params = reactive({info: '暂无描述'});
let rules = {
username: RULE.USERNAME,
password: RULE.PASSWORD,
realname: RULE.REALNAME,
idcard: RULE.IDCARD,
phone: RULE.PHONE,
email: RULE.EMAIL,
info: RULE.INFO
};
/* ==================== 添加成功后 ==================== */
function insertSuccess() {
ElMessage.success('添加记录成功!');
setTimeout(() => router.push('/User'), 1000);
}
</script>
<template>
<my-nav :items="navItems"/>
<el-card class="user-insert-card" header="添加用户">
<my-form type="insert"
:items="items"
:params="params"
:rules="rules"
:api="insertApi"
:args="{module: 'user'}"
:callback="insertSuccess"/>
</el-card>
</template>
<style scoped lang="scss">
.user-insert-card {
width: 60%; // 宽度
margin: 65px auto 0; // 外边距
}
</style>
3. 修改用户记录
心法:修改用户记录页面
武技:开发修改用户记录页面 views/ums/user/UserUpdate.vue
<script setup>
import router from "../../../router/index.js";
import MyNav from "../../../components/MyNav.vue";
import MyForm from "../../../components/MyForm.vue";
import MyUpload from "../../../components/MyUpload.vue";
import {MINIO_AVATAR, RULE} from "../../../const/index.js";
import {reactive, ref} from "vue";
import {updateApi} from "../../../api/index.js";
import {UPLOAD_AVATAR_URL} from "../../../api/ums/user.js";
import {GENDER_OPTIONS, ZODIAC_OPTIONS} from "../../../const/index.js";
import {ElMessage} from "element-plus";
// 获取当前用户记录
let user = JSON.parse(sessionStorage.getItem('row'));
let avatarUrl = ref(MINIO_AVATAR(user['avatar']));
// 路径导航
const navItems = [
{icon: 'User', label: '用户管理'},
{icon: 'User', label: '用户列表', url: '/User'},
{icon: 'Edit', label: '修改用户信息'},
];
/* ==================== 修改基本信息 ==================== */
// 表单项 + 表单值 + 表单规则
let updateFormItems = ref([
{label: '账号', prop: 'username', disabled: true, span: 12},
{label: '姓名', prop: 'realname', disabled: true, span: 12},
{label: '手机号码', prop: 'phone', disabled: true, span: 12},
{label: '身份证号', prop: 'idcard', disabled: true, span: 12},
{label: '昵称', prop: 'nickname', required: true, span: 12},
{label: '邮箱', prop: 'email', required: true, span: 12},
{label: '性别', prop: 'gender', required: true, span: 12, type: 'select', options: GENDER_OPTIONS},
{label: '年龄', prop: 'age', span: 12, type: 'number', required: true, },
{label: '星座', prop: 'zodiac', span: 12, type: 'select', options: ZODIAC_OPTIONS, required: true, },
{label: '省份', prop: 'province', span: 12, required: true, },
{label: '描述', prop: 'info', type: 'textarea', required: true, rows: 10},
]);
let updateFormParams= reactive(user);
let updateFormRules = {
nickname: RULE.NICKNAME,
email: RULE.EMAIL,
province: RULE.PROVINCE,
info: RULE.INFO,
};
/* ==================== 修改成功后 ==================== */
function updateSuccess() {
ElMessage.success('修改记录成功!');
setTimeout(() => router.push('/User'), 1000);
}
/* ==================== 上传成功后 ==================== */
function uploadAvatarSuccess(data) {
avatarUrl.value = MINIO_AVATAR(data);
}
</script>
<template>
<my-nav :items="navItems"/>
<div class="user-update-body">
<el-row :gutter="20">
<el-col :span="6">
<el-card class="user-info-card">
<el-image class="avatar-image" :src="avatarUrl"/>
</el-card>
<el-card class="upload-avatar-card" header="上传个人头像">
<my-upload :url="UPLOAD_AVATAR_URL + '/' + user['id']"
name="avatarFile"
:callback="uploadAvatarSuccess"
:autoUpload="true"/>
</el-card>
</el-col>
<el-col :span="18">
<el-card class="update-card" header="修改基本信息">
<my-form type="update"
:items="updateFormItems"
:params="updateFormParams"
:rules="updateFormRules"
:api="updateApi"
:args="{module: 'user'}"
:callback="updateSuccess"/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<style scoped lang="scss">
.user-update-body {
padding: 0 100px; // 内边距
margin: 65px auto 0; // 外边距
.user-info-card {
text-align: center; // 内容居中
.nickname {
margin-bottom: 5px; // 下边距
}
.avatar-image {
height: 170px; // 高度
}
}
.upload-avatar-card {
margin-top: 25px; // 上外边距
}
}
</style>
4. 重设用户角色
心法:重设用户角色页面
武技:开发重设用户角色页面 views/ums/user/UserUpdateRoles.vue
<script setup>
import MyNav from "../../../components/MyNav.vue";
import {onMounted, ref} from "vue";
import {ElMessage} from "element-plus";
import {getResponseData} from "../../../request/index.js";
import {listRoleIdsByUserIdApi, updateRolesByUserIdApi} from "../../../api/ums/role.js";
import {isNotNull} from "../../../util/index.js";
import {simpleListApi} from "../../../api/index.js";
// 获取当前用户主键和用户昵称
let userId = sessionStorage.getItem('userId');
let nickname = sessionStorage.getItem('nickname');
// 路径导航
const navItems = [
{icon: 'User', label: '用户管理'},
{icon: 'User', label: '用户列表', url: '/User'},
{icon: 'UserFilled', label: '为用户重设角色'},
];
// 全部角色 + 我的角色主键数组
let allRoles = ref([]);
let myRoleIds = ref([]);
/* ==================== 确认修改用户的角色 ==================== */
async function updateMyRoles() {
let data = getResponseData(await updateRolesByUserIdApi(userId, myRoleIds.value));
if (isNotNull(data)) {
ElMessage.success('角色重设成功,下次登录生效!');
}
}
/* ==================== 加载函数 ==================== */
onMounted(async () => {
// 查询全部角色
Object.values(getResponseData(await simpleListApi(null, {module: 'role'}))).forEach(role => {
allRoles.value.push({label: role['title'], key: role['id']});
});
// 查询该员工的角色
Object.values(getResponseData(await listRoleIdsByUserIdApi(userId))).forEach(roleIds => {
myRoleIds.value.push(roleIds);
});
});
</script>
<template v-if="allRoles">
<div class="user-roles-body">
<my-nav :items="navItems"/>
<el-transfer class="user-roles-transfer"
filterable filter-placeholder="输入关键字"
v-if="allRoles.length > 0"
v-model="myRoleIds"
:data="allRoles"
:titles="['全部可选角色', '当前已选角色']"
:props="{key: 'key', label: 'label'}"
:button-texts="['移除', '添加']">
<template #left-footer>
<el-text class="mx-1" type="info">tips: 请重新选择该用户的角色!</el-text>
</template>
<template #right-footer>
<el-button type="primary" @click="updateMyRoles" size="small">确认修改</el-button>
</template>
</el-transfer>
</div>
</template>
<style scoped lang="scss">
.user-roles-body {
text-align: center; // 内容居中
.user-roles-transfer {
margin-top: 65px; // 上外边距
}
}
:deep(.el-transfer-panel) {
width: 300px; // 宽度
}
:deep(.el-transfer-panel__body) {
height: 400px; // 高度
}
:deep(.el-transfer-panel__footer) {
text-align: center; // 内容居中
}
</style>
E02. 角色管理模块
1. 查看角色列表
心法:查看角色列表页面
武技:开发查看角色列表页面 views/ums/role/Role.vue
<script setup>
import MyNav from "../../../components/MyNav.vue";
import MyHead from "../../../components/MyHead.vue";
import MyTable from "../../../components/MyTable.vue";
import {onMounted, ref, reactive} from "vue";
import {myPage} from "../../../request";
import {deleteApi, deleteBatchApi, excelApi, pageApi} from "../../../api/index.js";
import {ElMessage} from "element-plus";
import router from "../../../router/index.js";
// 路径导航
const navItems = [
{icon: 'User', label: '用户管理'},
{icon: 'UserFilled', label: '角色列表'}
];
// 数据头
const headItems = [
{type: 'ipt', span: 5, placeholder: '搜索角色标题', callback: pageByTitle},
];
// 表格列
const columns = [
{label: '序号', prop: 'idx', type: 'tag'},
{label: '标题', prop: 'title'},
{label: '描述', prop: 'info', type: 'card', width: 520},
];
// 按钮列
const buttons = [
{label: '重设菜单', icon: 'Edit', callback: toRoleUpdateMenus},
];
/* ==================== 重设菜单 ==================== */
function toRoleUpdateMenus(row) {
router.push({
path: '/RoleUpdateMenus', query: {
roleId: row['id'],
roleTitle: row['title']
}
});
}
/* ==================== 分页查询 ==================== */
// 表格数据 + 分页数据 + 角色标题
let records = ref();
let pageInfo = reactive({pageNum: 1, pageSize: 5, total: 0, callback: page});
let roleTitle = ref();
async function page(pageNum = pageInfo['pageNum'], pageSize = pageInfo['pageSize']) {
// 分页参数
let config = {
api: pageApi,
args: {module: 'role'},
params: {pageNum, pageSize},
records, pageInfo
};
// 若角色标题不为空,则附加为分页条件
if (roleTitle.value) config['params']['title'] = roleTitle.value;
// 发送分页请求
await myPage(config);
}
/* ==================== 搜索角色标题 ==================== */
function pageByTitle(val) {
// 仅当输入框有值,或者当前处于按条件分页状态时,发送分页请求
if (val || roleTitle.value) {
roleTitle.value = val;
page();
}
}
/* ==================== 删除成功回调 ==================== */
function deleteSuccess() {
ElMessage.success('删除成功!');
page();
}
/* ==================== 报表打印 ==================== */
function downloadExcel() {
excelApi('/role/excel', '角色报表');
}
/* ==================== 加载函数 ==================== */
onMounted(() => page());
</script>
<template>
<my-nav :items="navItems"/>
<my-head :items="headItems"/>
<my-table module="role"
insert-page="/RoleInsert"
update-page="/RoleUpdate"
:records="records"
:columns="columns"
:buttons="buttons"
:delete-api="deleteApi"
:delete-batch-api="deleteBatchApi"
:delete-callback="deleteSuccess"
:excel-api="downloadExcel"
:pageInfo="pageInfo"/>
</template>
<style scoped lang="scss"></style>
2. 添加角色记录
心法:添加角色记录页面
武技:开发添加角色记录页面 views/ums/role/RoleInsert.vue
<script setup>
import MyForm from "../../../components/MyForm.vue";
import MyNav from "../../../components/MyNav.vue";
import {reactive, ref} from "vue";
import {insertApi} from "../../../api/index.js";
import {RULE} from "../../../const";
import {ElMessage} from "element-plus";
import router from "../../../router";
// 路径导航
const navItems = [
{icon: 'Avatar', label: '用户管理'},
{icon: 'UserFilled', label: '角色列表', url: '/Role'},
{icon: 'Plus', label: '添加新角色'},
]
// 表单项 + 表单值 + 表单规则
let items = ref([
{label: '标题', prop: 'title', required: true, span: 12},
{hidden: true, span: 12},
{label: '序号', prop: 'idx', type: 'number', required: true, span: 12},
{hidden: true, span: 12},
{label: '描述', prop: 'info', required: true, type: 'textarea'},
]);
let params = reactive({});
let rules = {title: RULE.TITLE, info: RULE.INFO};
/* ==================== 添加成功后 ==================== */
function insertSuccess() {
ElMessage.success('添加成功!');
setTimeout(() => router.push('/Role'), 1000);
}
</script>
<template>
<my-nav :items="navItems"/>
<el-card class="role-insert-card" header="添加新角色">
<my-form type="insert"
:items="items"
:params="params"
:rules="rules"
:api="insertApi"
:args="{module: 'role'}"
:callback="insertSuccess"/>
</el-card>
</template>
<style scoped lang="scss">
.role-insert-card {
width: 60%; // 宽度
margin: 65px auto 0; // 外边距
}
</style>
3. 修改角色记录
心法:修改角色记录页面
武技:开发修改角色记录页面 views/ums/role/RoleUpdate.vue
<script setup>
import MyForm from "../../../components/MyForm.vue";
import MyNav from "../../../components/MyNav.vue";
import {reactive, ref} from "vue";
import {updateApi} from "../../../api/index.js";
import {RULE} from "../../../const";
import {ElMessage} from "element-plus";
import router from "../../../router";
// 角色记录
let role = JSON.parse(sessionStorage.getItem('row'));
// 路径导航
const navItems = [
{icon: 'Avatar', label: '用户管理'},
{icon: 'UserFilled', label: '角色列表', url: '/Role'},
{icon: 'Edit', label: '修改角色'},
];
// 表单项 + 表单值 + 表单规则
let items = ref([
{label: '标题', prop: 'title', required: true, span: 12},
{hidden: true, span: 12},
{label: '序号', prop: 'idx', type: 'number', required: true, span: 12},
{hidden: true, span: 12},
{label: '描述', prop: 'info', required: true, type: 'textarea'},
]);
let params = reactive(role);
let rules = {title: RULE.TITLE, info: RULE.INFO};
/* ==================== 修改成功后 ==================== */
function updateSuccess() {
ElMessage.success('修改记录成功!');
setTimeout(() => router.push('/Role'), 1000);
}
</script>
<template>
<my-nav :items="navItems"/>
<el-card class="role-update-card" header="修改角色信息">
<my-form type="update"
:items="items"
:params="params"
:rules="rules"
:api="updateApi"
:args="{module: 'role'}"
:callback="updateSuccess"/>
</el-card>
</template>
<style scoped lang="scss">
.role-update-card {
width: 60%; // 宽度
margin: 65px auto 0; // 外边距
}
</style>
4. 重设角色菜单
心法:重设角色菜单页面
武技:开发重设角色菜单页面 views/ums/role/RoleUpdateMenus.vue
<script setup>
import MyNav from "../../../components/MyNav.vue";
import {getResponseData} from "../../../request";
import {onMounted, ref} from "vue";
import {simpleListApi} from "../../../api/index.js";
import {listMenuIdsByRoleIdApi, updateMenusByRoleIdApi} from "../../../api/ums/menu.js";
import {ElMessage} from "element-plus";
import router from "../../../router";
// 获取路由中的角色主键和角色标题
let roleId = router.currentRoute.value.query['roleId'];
let roleTitle = router.currentRoute.value.query['roleTitle'];
// 路径导航
const navItems = [
{icon: 'Avatar', label: '用户管理'},
{icon: 'UserFilled', label: '角色列表', url: '/Role'},
{icon: 'Edit', label: '为角色重设菜单'},
];
// 全部菜单 + 我的菜单主键数组
let allMenus = ref([]);
let myMenuIds = ref([]);
// 菜单的父子关系
let idToPidMap = {};
/* ==================== 确认修改菜单 ==================== */
/**
* 处理选中的菜单ID列表
*
* 1. 遍历处理选中的菜单ID列表。
* 2. 将选中的菜单ID追加到临时数组 result 中。
* 3. 根据菜单的父子关系 idToPidMap 对象获取该菜单的父菜单ID。
* 4. 若临时数组 result 中已存在相同的父菜单ID,则不会重复追加。
* 5. 若临时数组 result 中不存在相同的父菜单ID,则追加到临时数组 result 中。
*
* @param menuIds 选中的菜单列表
* @return result 处理后的菜单ID列表,包括全部子菜单的ID和对应父菜单的ID,不存在重复项。
*/
function buildMenuIds(menuIds) {
let result = [];
for (let i in menuIds) {
result.push(menuIds[i]);
let parentMenuId = idToPidMap[menuIds[i]];
if (result.indexOf(parentMenuId) === -1) {
result.push(parentMenuId);
}
}
return result;
}
async function updateMenus() {
let data = getResponseData(await updateMenusByRoleIdApi(roleId, buildMenuIds(myMenuIds.value)));
if (data) {
ElMessage.success('菜单重设成功,下次登录生效!');
}
}
/* ==================== 加载函数 ==================== */
onMounted(async () => {
// 查询全部菜单
Object.values(getResponseData(await simpleListApi(null, {module: 'menu'}))).forEach(menu => {
// 记录菜单的父子关系: {id01: pid01, id02: pid02 .. }
idToPidMap[menu['id']] = menu['pid'];
// 组装 ElTransfer 数据
if (menu['pid'] !== 0) {
allMenus.value.push({label: menu['parentTitle'] + ' / ' + menu['title'], key: menu['id']});
}
});
// 查询该角色的菜单
Object.values(getResponseData(await listMenuIdsByRoleIdApi(roleId))).forEach(menuIds => {
myMenuIds.value.push(menuIds);
});
});
</script>
<template>
<div class="role-menus-body">
<my-nav :items="navItems"/>
<el-transfer class="role-menus-transfer"
filterable filter-placeholder="输入关键字"
v-if="allMenus.length > 0"
v-model="myMenuIds"
:data="allMenus"
:titles="['全部可选菜单', '【' + roleTitle + '】已选菜单']"
:props="{key: 'key', label: 'label'}"
:button-texts="['移除', '添加']">
<template #left-footer>
<el-text class="mx-1" type="info">tips: 请重新选择该角色的菜单!</el-text>
</template>
<template #right-footer>
<el-button type="primary" @click="updateMenus" size="small">确认修改</el-button>
</template>
</el-transfer>
</div>
</template>
<style scoped lang="scss">
.role-menus-body {
text-align: center; // 内容居中
.role-menus-transfer {
margin-top: 65px; // 上外边距
}
}
:deep(.el-transfer-panel) {
width: 300px; // 宽度
}
:deep(.el-transfer-panel__body) {
height: 400px; // 高度
}
:deep(.el-transfer-panel__footer) {
text-align: center; // 内容居中
}
</style>
E03. 菜单管理模块
1. 查看父菜单列表
心法:查看父菜单列表页面
武技:开发查看父菜单列表页面 views/ums/menu/Menu.vue
<script setup>
import MyNav from "../../../components/MyNav.vue";
import MyHead from "../../../components/MyHead.vue";
import MyTable from "../../../components/MyTable.vue";
import {onMounted, ref, reactive} from "vue";
import {myPage} from "../../../request";
import {deleteApi, deleteBatchApi, excelApi, pageApi} from "../../../api/index.js";
import {ElMessage} from "element-plus";
import router from "../../../router/index.js";
// 路径导航
const navItems = [
{icon: 'Avatar', label: '用户管理'},
{icon: 'Menu', label: '菜单列表(父菜单)'}
];
// 数据头
const headItems = [
{type: 'ipt', span: 5, placeholder: '搜索父菜单名', callback: pageByTitle},
];
// 表格列
const columns = [
{label: '序号', prop: 'idx', type: 'tag'},
{label: '图标', prop: 'icon', type: 'icon', width: 90},
{label: '名称', prop: 'title', width: 120},
{label: '描述', prop: 'info', type: 'card', width: 520},
];
// 按钮列
const buttons = [
{label: '下级菜单', icon: 'Menu', callback: toSubMenu},
];
/* ==================== 跳转下级菜单页面 ==================== */
function toSubMenu(row) {
sessionStorage.setItem('pid', row['id']);
sessionStorage.setItem('parentTitle', row['title']);
router.push('/SubMenu');
}
/* ==================== 分页查询 ==================== */
// 表格数据 + 分页数据 + 菜单名称
let records = ref();
let pageInfo = reactive({pageNum: 1, pageSize: 5, total: 0, callback: page});
let menuTitle = ref();
async function page(pageNum = pageInfo['pageNum'], pageSize = pageInfo['pageSize']) {
// 分页参数(额外添加 pid=0 保证仅查询父菜单)
let config = {
api: pageApi,
args: {module: 'menu'},
params: {pageNum, pageSize, pid: 0},
records, pageInfo
};
// 若菜单名不为空,则附加为分页条件
if (menuTitle.value) config['params']['title'] = menuTitle.value;
// 发送分页请求
await myPage(config);
}
/* ==================== 搜索菜单名 ==================== */
function pageByTitle(val) {
// 仅当输入框有值,或者当前处于按条件分页状态时,发送分页请求
if (val || menuTitle.value) {
menuTitle.value = val;
page();
}
}
/* ==================== 删除成功回调 ==================== */
function deleteSuccess() {
ElMessage.success('删除成功!');
page();
}
/* ==================== 报表打印 ==================== */
function downloadExcel() {
excelApi('/menu/excel', '菜单报表');
}
/* ==================== 加载函数 ==================== */
onMounted(() => page());
</script>
<template>
<my-nav :items="navItems"/>
<my-head :items="headItems"/>
<my-table module="menu"
insert-page="/MenuInsert"
update-page="/MenuUpdate"
:records="records"
:columns="columns"
:buttons="buttons"
:delete-api="deleteApi"
:delete-batch-api="deleteBatchApi"
:delete-callback="deleteSuccess"
:excel-api="downloadExcel"
:pageInfo="pageInfo"/>
</template>
<style scoped lang="scss"></style>
2. 添加父菜单记录
心法:添加父菜单记录页面
武技:开发添加父菜单记录页面 views/ums/menu/MenuInsert.vue
<script setup>
import MyForm from "../../../components/MyForm.vue";
import MyNav from "../../../components/MyNav.vue";
import {reactive, ref} from "vue";
import {insertApi} from "../../../api/index.js";
import {RULE} from "../../../const";
import {ElMessage} from "element-plus";
import router from "../../../router";
// 路径导航
const navItems = [
{icon: 'Avatar', label: '用户管理'},
{icon: 'Menu', label: '菜单列表(父菜单)', url: '/Menu'},
{icon: 'Plus', label: '添加新菜单(父菜单)'},
]
// 表单项 + 表单值 + 表单规则
let items = ref([
{label: '名称', prop: 'title', required: true, span: 12},
{label: '序号', prop: 'idx', type: 'number', required: true, span: 12},
{label: '图标', prop: 'icon', required: true, type: 'icon'},
{label: '描述', prop: 'info', required: true, type: 'textarea'},
]);
let params = reactive({pid: 0, url: '/'});
let rules = {title: RULE.TITLE, info: RULE.INFO};
/* ==================== 添加成功后 ==================== */
function insertSuccess() {
ElMessage.success('添加记录成功!');
setTimeout(() => router.push('/Menu'), 1000);
}
</script>
<template>
<my-nav :items="navItems"/>
<el-card class="menu-insert-card" header="添加新菜单(父菜单)">
<my-form type="insert"
:items="items"
:params="params"
:rules="rules"
:api="insertApi"
:args="{module: 'menu'}"
:callback="insertSuccess"/>
</el-card>
</template>
<style scoped lang="scss">
.menu-insert-card {
width: 60%; // 宽度
margin: 65px auto 0; // 外边距
}
</style>
3. 修改父菜单记录
心法:修改父菜单记录页面
武技:开发修改父菜单记录页面 views/ums/menu/MenuUpdate.vue
<script setup>
import MyForm from "../../../components/MyForm.vue";
import MyNav from "../../../components/MyNav.vue";
import {reactive, ref} from "vue";
import {updateApi} from "../../../api/index.js";
import {RULE} from "../../../const";
import {ElMessage} from "element-plus";
import router from "../../../router";
// 菜单记录
let menu = JSON.parse(sessionStorage.getItem('row'));
// 路径导航
const navItems = [
{icon: 'Avatar', label: '用户管理'},
{icon: 'Menu', label: '菜单列表(父菜单)', url: '/Menu'},
{icon: 'Edit', label: '修改菜单(父菜单)'},
];
// 表单项 + 表单值 + 表单规则
let items = ref([
{label: '名称', prop: 'title', required: true, span: 12},
{label: '序号', prop: 'idx', type: 'number', required: true, span: 12},
{label: '图标', prop: 'icon', required: true, type: 'icon'},
{label: '描述', prop: 'info', required: true, type: 'textarea'},
]);
let params = reactive(menu);
let rules = {title: RULE.TITLE, info: RULE.INFO};
/* ==================== 修改成功后 ==================== */
function updateSuccess() {
ElMessage.success('修改记录成功!');
setTimeout(() => router.push('/Menu'), 1000);
}
</script>
<template>
<my-nav :items="navItems"/>
<el-card class="menu-update-card" header="修改菜单(父菜单)">
<my-form type="update"
:items="items"
:params="params"
:rules="rules"
:api="updateApi"
:args="{module: 'menu'}"
:callback="updateSuccess"/>
</el-card>
</template>
<style scoped lang="scss">
.menu-update-card {
width: 60%; // 宽度
margin: 65px auto 0; // 外边距
}
</style>
4. 查看子菜单列表
心法:查看子菜单列表页面
武技:开发查看子菜单列表页面 views/ums/menu/sub/SubMenu.vue
<script setup>
import MyNav from "../../../../components/MyNav.vue";
import MyHead from "../../../../components/MyHead.vue";
import MyTable from "../../../../components/MyTable.vue";
import {onMounted, ref, reactive} from "vue";
import {myPage} from "../../../../request";
import {deleteApi, deleteBatchApi, excelApi, pageApi} from "../../../../api/index.js";
import {ElMessage} from "element-plus";
// 父菜单ID和父菜单名称
const pid = sessionStorage.getItem('pid');
const parentTitle = sessionStorage.getItem('parentTitle');
// 路径导航
const navItems = [
{icon: 'Avatar', label: '用户管理'},
{icon: 'Menu', label: '菜单列表(父菜单)', url: '/Menu'},
{icon: 'Menu', label: '菜单列表(子菜单)'}
];
// 数据头
const headItems = [
{type: 'ipt', span: 5, placeholder: '搜索子菜单名', callback: pageByTitle},
];
// 表格列
const columns = [
{label: '父菜单', prop: 'parentMenu.title', width: 90, sortable: false},
{label: '序号', prop: 'idx', type: 'tag'},
{label: '图标', prop: 'icon', type: 'icon', width: 90},
{label: '名称', prop: 'title', width: 120},
{label: '地址', prop: 'url', width: 120},
{label: '描述', prop: 'info', type: 'card', width: 520},
];
/* ==================== 分页查询 ==================== */
// 表格数据 + 分页数据 + 菜单名称
let records = ref();
let pageInfo = reactive({pageNum: 1, pageSize: 5, total: 0, callback: page});
let menuTitle = ref();
async function page(pageNum = pageInfo['pageNum'], pageSize = pageInfo['pageSize']) {
// 分页参数
let config = {
api: pageApi,
args: {module: 'menu'},
params: {pageNum, pageSize, pid},
records, pageInfo
};
// 若菜单名不为空,则附加为分页条件
if (menuTitle.value) config['params']['title'] = menuTitle.value;
// 发送分页请求
await myPage(config);
}
/* ==================== 搜索菜单名 ==================== */
function pageByTitle(val) {
// 仅当输入框有值,或者当前处于按条件分页状态时,发送分页请求
if (val || menuTitle.value) {
menuTitle.value = val;
page();
}
}
/* ==================== 删除成功回调 ==================== */
function deleteSuccess() {
ElMessage.success('删除成功!');
page();
}
/* ==================== 报表打印 ==================== */
function downloadExcel() {
excelApi('/menu/excel', '菜单报表');
}
/* ==================== 加载函数 ==================== */
onMounted(() => page());
</script>
<template>
<my-nav :items="navItems"/>
<my-head :items="headItems"/>
<my-table module="menu"
insert-page="/SubMenuInsert"
update-page="/SubMenuUpdate"
:records="records"
:columns="columns"
:delete-api="deleteApi"
:delete-batch-api="deleteBatchApi"
:delete-callback="deleteSuccess"
:excel-api="downloadExcel"
:pageInfo="pageInfo"/>
</template>
<style scoped lang="scss"></style>
5. 添加子菜单记录
心法:添加子菜单记录页面
武技:开发添加子菜单记录页面 views/ums/menu/sub/SubMenuInsert.vue
<script setup>
import MyForm from "../../../../components/MyForm.vue";
import MyNav from "../../../../components/MyNav.vue";
import {reactive, ref} from "vue";
import {insertApi} from "../../../../api/index.js";
import {RULE} from "../../../../const";
import {ElMessage} from "element-plus";
import router from "../../../../router";
// 父菜单ID和父菜单名称
const pid = sessionStorage.getItem('pid');
const parentTitle = sessionStorage.getItem('parentTitle');
// 路径导航
const navItems = [
{icon: 'Avatar', label: '用户管理'},
{icon: 'Menu', label: '菜单列表(父菜单)', url: '/Menu'},
{icon: 'Menu', label: '菜单列表(子菜单)', url: '/SubMenu'},
{icon: 'Plus', label: '添加新菜单(子菜单)'},
]
// 表单项 + 表单值 + 表单规则
let items = ref([
{label: '名称', prop: 'title', required: true, span: 12},
{label: '序号', prop: 'idx', type: 'number', required: true, span: 12},
{label: '地址', prop: 'url', required: true},
{label: '图标', prop: 'icon', required: true, type: 'icon'},
{label: '描述', prop: 'info', required: true, type: 'textarea', rows: 6},
]);
let params = reactive({pid});
let rules = {title: RULE.TITLE, info: RULE.INFO};
/* ==================== 添加成功后 ==================== */
function insertSuccess() {
ElMessage.success('添加成功!');
setTimeout(() => router.push('/SubMenu'), 1000);
}
</script>
<template>
<my-nav :items="navItems"/>
<el-card class="menu-insert-card" :header="`为【${parentTitle}】添加子菜单`">
<my-form type="insert"
:items="items"
:params="params"
:rules="rules"
:api="insertApi"
:args="{module: 'menu'}"
:callback="insertSuccess"/>
</el-card>
</template>
<style scoped lang="scss">
.menu-insert-card {
width: 60%; // 宽度
margin: 65px auto 0; // 外边距
}
</style>
6. 修改子菜单记录
心法:修改子菜单记录页面
武技:开发修改子菜单记录页面 views/ums/menu/sub/SubMenuUpdate.vue
<script setup>
import MyForm from "../../../../components/MyForm.vue";
import MyNav from "../../../../components/MyNav.vue";
import {reactive, ref} from "vue";
import {updateApi} from "../../../../api/index.js";
import {RULE} from "../../../../const";
import {ElMessage} from "element-plus";
import router from "../../../../router";
// 菜单记录
let menu = JSON.parse(sessionStorage.getItem('row'));
// 父菜单ID和父菜单名称
const pid = sessionStorage.getItem('pid');
const parentTitle = sessionStorage.getItem('parentTitle');
menu['pid'] = pid;
menu['parentTitle'] = parentTitle;
// 路径导航
const navItems = [
{icon: 'Avatar', label: '用户管理'},
{icon: 'Menu', label: '菜单列表(父菜单)', url: '/Menu'},
{icon: 'Menu', label: '菜单列表(子菜单)', url: '/SubMenu'},
{icon: 'Edit', label: '修改菜单(子菜单)'},
];
// 表单项 + 表单值 + 表单规则
let items = ref([
{label: '父菜单', prop: 'parentTitle', disabled: true},
{label: '名称', prop: 'title', required: true, span: 12},
{label: '序号', prop: 'idx', type: 'number', required: true, span: 12},
{label: '地址', prop: 'url', required: true},
{label: '图标', prop: 'icon', required: true, type: 'icon'},
{label: '描述', prop: 'info', required: true, type: 'textarea', rows: 5},
]);
let params = reactive(menu);
let rules = {title: RULE.TITLE, info: RULE.INFO};
/* ==================== 修改成功后 ==================== */
function updateSuccess() {
ElMessage.success('修改记录成功!');
setTimeout(() => router.push('/SubMenu'), 1000);
}
</script>
<template>
<my-nav :items="navItems"/>
<el-card class="menu-update-card" header="修改菜单(子菜单)">
<my-form type="update"
:items="items"
:params="params"
:rules="rules"
:api="updateApi"
:args="{module: 'menu'}"
:callback="updateSuccess"/>
</el-card>
</template>
<style scoped lang="scss">
.menu-update-card {
width: 60%; // 宽度
margin: 65px auto 0; // 外边距
}
</style>
Java道经 - 项目 - MyLesson - 后台前端(二)