JP4-7-MyLesson后台前端(二)

发布于:2025-09-07 ⋅ 阅读:(18) ⋅ 点赞:(0)

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 - 后台前端(二)


网站公告

今日签到

点亮在社区的每一天
去签到