挑战一周完成Vue3项目Day4: 用户管理+角色管理+菜单管理+首页+暗黑模式/主题切换

一、用户管理

1.静态搭建

src/views/acl/user/index.vue

<template>
    <el-card style="height:80px;">
        <el-form :inline="true" class="form">
            <el-form-item label="用户名:">
                <el-input placeholder="请你输入用户名"></el-input>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" size="default">搜索</el-button>
                <el-button type="primary" size="default">重置</el-button>
            </el-form-item>
        </el-form>
    </el-card>
    <el-card style="margin:10px 0;">
        <el-button type="primary" size="default">添加用户</el-button>
        <el-button type="primary" size="default">批量删除</el-button>
        <!-- table展示表单信息 -->
        <el-table style="margin:10px 0;" border>
            <el-table-column type="selection" align="center"></el-table-column>
            <el-table-column label="#" align="center"></el-table-column>
            <el-table-column label="ID" align="center"></el-table-column>
            <el-table-column label="用户名字" align="center"></el-table-column>
            <el-table-column label="用户名称" align="center"></el-table-column>
            <el-table-column label="用户角色" align="center"></el-table-column>
            <el-table-column label="创建时间" align="center"></el-table-column>
            <el-table-column label="更新时间" align="center"></el-table-column>
            <el-table-column label="操作" width="260px" align="center"></el-table-column>
        </el-table>
        <!-- 分页器 -->
        <el-pagination
          v-model:current-page="pageNo"
          v-model:page-size="pageSize"
          :page-sizes="[3,5,7,9]"
          :background="true"
          layout="prev, pager, next, jumper,->,size,total"
          :total="400"/>
    </el-card>
</template>

<script setup lang="ts">
import {ref} from 'vue';
// 展示当前页
let pageNo=ref<number>(1)
// 每页几个数据
let pageSize=ref<number>(5)
</script>

<style scoped>
.form{
    display: flex;
    justify-content: space-between;
    align-items: center;
}
</style>

2.业务实现流程

2.1展示已有账号的数据 

1.一上来就发请求获取数据,点击不同的页码也要发请求
2.定义接口数据
3.获取数据
4.存储数据
5.展示数据(在table和分页器都要展示)

2.2添加与修改用户 

1.静态搭建
抽屉结构》drawer
添加点击按钮和编辑按钮弹出抽屉事件

 2.完成添加业务
(1)添加地址接口(判断携带参数有无id)和定义数据
(2)收集数据用v-model收集到定义好的userParams里面。(定义参数userParams收集用户信息的响应式数据)
(3)添加点击保存按钮回调save.(code=200判断添加/更新有无id,关闭抽屉并弹出相应的提示信息,再次获取最新数据getHasUser)
(4)添加取消按钮的回调cancel.
(5)每次点击确定或者取消都要清空数据:在addUser里面清空数据

3.表单校验功能
(1)校验username,name,password
(2)给抽屉的身体部分添加校验规则:rules="rules"(告诉表单数据数据收集:model="userParams"并给表单项添加prop)
(3)校验规则


// 校验用户名字回调函数
const validatorUsername = (rule: any, value: any, callBack: any) => {
  // 用户名字|昵称,长度至少五位
  if (value.trim().length >= 5) {
    callBack()
  } else {
    callBack(new Error('用户名字至少五位'))
  }
}
// 校验用户昵称回调函数
const validatorName = (rule: any, value: any, callBack: any) => {
  // 用户名字|昵称,长度至少五位
  if (value.trim().length >= 5) {
    callBack()
  } else {
    callBack(new Error('用户昵称至少五位'))
  }
}
// 校验用户名字回调函数
const validatorPassword = (rule: any, value: any, callBack: any) => {
  // 用户名字|昵称,长度至少六位
  if (value.trim().length >= 6) {
    callBack()
  } else {
    callBack(new Error('用户密码至少六位'))
  }
}
// 表单校验的规则对象
const rules = {
  // 用户名字
  username: [{ required: true, trigger: 'blur', validator: validatorUsername }],
  // 用户昵称
  name: [{ required: true, trigger: 'blur', validator: validatorName }],
  // 用户密码
  password: [{ required: true, trigger: 'blur', validator: validatorPassword }]
}

(4)注意:务必要在点击addUaer时进行校验

// 点击保存按钮的时候,务必需要保证表单全部符合条件再去发请求
  await formRef.value.validate()

(5)用nexttick和清空表单校验信息在addUser内
 

// 清除上一次的错误提示信息
  nextTick(() => {
    formRef.value.clearValidate('username')
    formRef.value.clearValidate('name')
    formRef.value.clearValidate('password')
  })

(6)注意触发时机,trigger要用blur不用change(因为会清空数据自动发生变化,无法清空校验错误信息)

 4.更新业务完成
(1)动态展示标题和是否有密码

<template #header>
      <h4>{{ userParams.id ? '更新用户' : '添加用户' }}</h4>
    </template>
 <el-form-item label="用户密码" prop="password" v-if="!userParams.id">

(2)updateUser回调记得清空数据

// 更新已有的用户按钮的回调
// row:即为已有用户的账号信息
const updateUser = (row: User) => {
  // 抽屉显示出来
  drawer.value = true
  // 存储收集已有的账号xinx
  Object.assign(userParams, row)
  // 清除上一次的错误提示信息
  nextTick(() => {
    formRef.value.clearValidate('username')
    formRef.value.clearValidate('name')
  })
}

(3)判断添加留在第一页、更新停留在当前页。在save内:

再添加了window刷新之后,这个可要可不要

// 获取最新的全部账号信息
 getHasUser(userParams.id ? pageNo.value : 1)

(4)解决修改到当前登录用户信息需要重新登录的问题,在save内:

// 浏览器自动刷新一次(为了解决修改到当前登录用户信息需要重新登录的问题)
    window.location.reload()

2.3 分配角色

1.静态搭建
》抽屉el-drwer》全选框el-checkbox》复选框el-checkbox-group
点击分配角色按钮出现抽屉结构@click="setRole(row)"
展示用户姓名username信息:存储数据》展示数据》禁用效果

<!-- 抽屉结构:用于某个已有账号进行职位分配 -->
  <el-drawer v-model="drawer1">
    <template #header>
      <h4>分配角色用户</h4>
    </template>
    <template #default>
      <el-form>
        <el-form-item label="用户姓名">
          <el-input v-model="userParams.username" :disabled="true"></el-input>
        </el-form-item>
        <el-form-item label="角色列表">
          <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">全选</el-checkbox>
          <!-- 显示职位的复选框 -->
          <el-checkbox-group v-model="userRole" @change="handleCheckedCitiesChange">
            <el-checkbox v-for="(role, index) in allRole" :key="index" :label="role">{{ role.roleName }}</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
      </el-form>
 
    </template>
    <template #footer>
      <div style="flex: auto">
        <el-button @click="drawer1 = false">取消</el-button>
        <el-button type="primary" @click="confirmClick">确认</el-button>
      </div>
    </template>
  </el-drawer>

2.分配角色业务
(1)定义API接口和方法,返回数据的ts类型
(2)获取全部职位数据allRolesList和当前用户职位的数据assignRoles:setRole里
(3)存储数据:
(4)展示所有职位:通过v-for和role.roleName展示
(5)展示勾选当前用户角色:v-model="userRole"
(6)全选、全不选效果,不确定样式

// 分配角色按钮的回调
const setRole = async (row: User) => {
  // 存储已有的用户信息
  Object.assign(userParams, row)
  //获取全部的职位的数据与当前用户已有的职位的数据
  let result: AllRoleResponseData = await reqAllRole((userParams.id as number))
  if (result.code === 200) {
    //存储全部的职位
    allRole.value = result.data.allRolesList
    //存储当前用户已有的职位  
    userRole.value = result.data.assignRoles
    // 抽屉显示出来
    drawer1.value = true
  }
}
 
//收集顶部复选框全选数据
const checkAll = ref<boolean>(false)
//控制顶部全选复选框不确定的样式
const isIndeterminate = ref<boolean>(true)
//存储全部职位的数据
let allRole = ref<AllRole>([])
//当前用户已有的职位
let userRole = ref<AllRole>([])
//顶部的全部复选框的change事件
const handleCheckAllChange = (val: boolean) => {
  //val:true(全选)|false(没有全选)
  userRole.value = val ? allRole.value : []
  //不确定的样式(确定样式)
  isIndeterminate.value = false
}
//顶部全部的复选框的change事件
const handleCheckedCitiesChange = (value: string[]) => {
  //顶部复选框的勾选数据
  //代表:勾选上的项目个数与全部的职位个数相等,顶部的复选框勾选上
  checkAll.value = value.length === allRole.value.length
  //不确定的样式
  isIndeterminate.value = value.length !== allRole.value.length
}

3.点击确定取消业务
(1)定义接口方法,ts类型
(2)取消按钮
<el-button @click="drawer1 = false">取消</el-button>
(3)确定按钮@click="confirmClick"
需要收集参数(当前用户id,收集到的职位id)》分配用户职位(判断,提示信息,获取更新完毕用户的信息,更新后留在当前页)

// 确定按钮的回调(分配职位)
const confirmClick = async () => {
  // 收集参数
  let data: SetRoleData = {
    userId: (userParams.id as number),
    roleIdList: userRole.value.map(item => {
      return (item.id as number)
    })
  }
  // 分配用户的职位
  let result: any = await reqSetUserRole(data)
  if (result.code === 200) {
    // 提示信息
    ElMessage({
      type: 'success',
      message: '分配职务成功'
    })
    // 关闭抽屉
    drawer1.value = false
    // 获取更新完毕用户的信息,更新完毕留在当前页
    getHasUser(pageNo.value)
  }
}

2.4删除业务 

(1)定义删除某一个账号和批量删除的API接口、方法,ts数据类型定义
(2)气泡确认框

<el-popconfirm :title="`你确定要删除${row.username}?`" width="260px" @confirm="deleteUser(row.id)">
            <template #reference>
              <el-button type="primary" size="small" icon="Delete">删除</el-button>
            </template>
          </el-popconfirm>

(3)删除某一个用户
绑定事件@confirm="deleteUser(row.id)"(要根据id去删除)
发送请求判断是否成功之后,再捞一次数据,判断回到当前页还是上一页

// 删除某一个用户
const deleteUser = async (userId: number) => {
  let result: any = await reqRemoveUser(userId)
  if (result.code === 200) {
    ElMessage({
      type: 'success',
      message: '删除成功'
    })
    getHasUser(userArr.value.length > 1 ? pageNo.value : pageNo.value - 1)
  }
}

(4)批量删除
在table身上绑定@selection-change="selectChange"》选中和存储批量删除的用户Id
给批量删除按钮判断是否需要禁用效果,:disabled="selectIdArr.length?false:true"并绑定点击事件deleteSelectUser

//准备一个数组存储批量删除的用户的ID
let selectIdArr = ref<User[]>([])
//table复选框勾选的时候会触发的事件
const selectChange = (value: any) => {
  selectIdArr.value = value
}
 
// 批量删除按钮的回调
const deleteSelectUser = async () => {
  //整理批量删除的参数
  let idsList: any = selectIdArr.value.map(item => {
    return item.id
  })
  //批量删除的请求
  let result: any = await reqSelectUser(idsList)
  if (result.code === 200) {
    ElMessage({
      type: 'success',
      message: '删除成功'
    })
    getHasUser(userArr.value.length > 1 ? pageNo.value : pageNo.value - 1)
  }
}

2.5搜索业务

(1)收集用户输入进来的关键字
        

<el-input placeholder="请输入用户名称" v-model="keyword"></el-input>
//定义响应式数据:收集用户输入进来的关键字
let keyword = ref<string>('')

(2)搜索按钮添加禁用效果
:disabled="keyword?false:true"
(3)搜索按钮绑定事件search
要注意的是,要给获取用户账号信息的接口添加一个username字段,才可以搜索

// 获取用户账号信息的接口
export const reqUserInfo = (page: number, limit: number, username: string) => request.get<any, UserResponseData>(API.ALLUSER_URL + `${page}/${limit}/?username=${username}`)
// 搜索按钮的回调
const search = () => {
  //根据关键字获取相应的用户数据
  getHasUser()
  //清空关键字
  keyword.value = ''
}

(4)重置按钮reset
要引入之前的仓库,来刷新

import useLayOutSettingStore from '@/store/modules/setting'
//获取模板setting仓库
let settingStore = useLayOutSettingStore()
//重置按钮
const reset = () => {
  settingStore.refresh = !settingStore.refresh
}

3.代码 

src/api/acl/user/index.ts

// 用户管理模块的接口
import request from '@/utils/request'
import type {
    AllRoleResponseData,
    SetRoleData,
    User,
    UserResponseData
} from './type'
enum API {
    // 获取全部已有用户账号信息
    ALLUSER_URL = '/admin/acl/user/',
    // 添加一个新的用户账号
    ADDUSER_URL = '/admin/acl/user/save',
    // 更新已有的用户账号
    UPDATEUSER_URL = '/admin/acl/user/update',
    //获取全部职位,当前账号拥有的职位接口
    ALLROLEURL = '/admin/acl/user/toAssign/',
    //给已有的用户分配角色接口
    SETROLE_URL = '/admin/acl/user/doAssignRole',
    //删除某一个账号
    DELETEUSER_URL = '/admin/acl/user/remove/',
    //批量删除的接口
    DELETEALLUSER_URL = '/admin/acl/user/batchRemove',
}
// 获取用户账号信息的接口
export const reqUserInfo = (page: number, limit: number, username: string) => request.get<any, UserResponseData>(API.ALLUSER_URL + `${page}/${limit}/?username=${username}`)
// 添加用户与更新已有用户的接口
export const reqAddOrUpdateUser = (data: User) => {
    // 携带参数有ID更新
    if (data.id) {
        return request.put<any, any>(API.UPDATEUSER_URL, data)
    } else {
        return request.post<any, any>(API.ADDUSER_URL, data)
    }
}//获取全部职位以及包含当前用户的已有的职位
export const reqAllRole = (userId: number) =>
    request.get<any, AllRoleResponseData>(API.ALLROLEURL + userId)
//分配职位
export const reqSetUserRole = (data: SetRoleData) =>
    request.post<any, any>(API.SETROLE_URL, data)
//删除某一个账号的信息
export const reqRemoveUser = (userId: number) =>
    request.delete<any, any>(API.DELETEUSER_URL + userId)
//批量删除的接口
export const reqSelectUser = (idList: number[]) =>
    request.delete(API.DELETEALLUSER_URL, { data: idList })

 src/api/acl/user/type.ts 

//账号信息的ts类型
export interface ResponseData {
    code: number
    message: string
    ok: boolean
  }
  //代表一个账号信息的ts类型
  export interface User {
    id?: number
    createTime?: string
    updateTime?: string
    username?: string
    password?: string
    name?: string
    phone?: null
    roleName?: string
  }
  //数组包含全部的用户信息
  export type Records = User[]
  //获取全部用户信息接口返回的数据ts类型
  export interface UserResponseData extends ResponseData {
    data: {
      records: Records
      total: number
      size: number
      current: number
      pages: number
    }
  }
  
  //代表一个职位的ts类型
  export interface RoleData {
    id?: number
    createTime?: string
    updateTime?: string
    roleName: string
    remark: null
  }
  //全部职位的列表
  export type AllRole = RoleData[]
  //获取全部职位的接口返回的数据ts类型
  export interface AllRoleResponseData extends ResponseData {
    data: {
      assignRoles: AllRole
      allRolesList: AllRole
    }
  }
  
  //给用户分配职位接口携带参数的ts类型
  export interface SetRoleData {
    roleIdList: number[]
    userId: number
  }

src/views/acl/user/index.vue   

<template>
  <el-card style="height: 80px;">
    <el-form :inline="true" class="form">
      <el-form-item label="用户名:">
        <el-input placeholder="请输入用户名称" v-model="keyword"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="search">搜索</el-button>
        <el-button type="primary" plain @click="reset">重置</el-button>
      </el-form-item>
    </el-form>
  </el-card>
  <el-card style="margin: 10px 0;">
    <el-button type="primary" size="default" @click="addUser">添加用户</el-button>
    <el-button type="danger" size="default" @click="deleteSelectUser">批量删除</el-button>
    <!-- table展示用户信息 -->
    <el-table @selection-change="selectChange" style="margin: 10px 0;" border :data="userArr">
      <el-table-column type="selection" align="center"></el-table-column>
      <el-table-column label="#" align="center" type="index"></el-table-column>
      <el-table-column label="id" prop="id"></el-table-column>
      <el-table-column label="用户名字" prop="username" show-overflow-tooltip></el-table-column>
      <el-table-column label="用户名称" prop="name" show-overflow-tooltip></el-table-column>
      <el-table-column label="用户角色" prop="roleName" show-overflow-tooltip></el-table-column>
      <el-table-column label="创建时间" prop="createTime" show-overflow-tooltip></el-table-column>
      <el-table-column label="更新时间" prop="updateTime" show-overflow-tooltip></el-table-column>
      <el-table-column label="操作" width="300px" align="center">
        <template #="{ row, $index }">
          <el-button type="primary" size="small" icon="User" @click="setRole(row)">分配角色</el-button>
          <el-button type="primary" size="small" icon="Edit" @click="updateUser(row)">编辑</el-button>
          <el-popconfirm :title="`你确定要删除${row.username}?`" width="260px" @confirm="deleteUser(row.id)">
            <template #reference>
              <el-button type="primary" size="small" icon="Delete">删除</el-button>
            </template>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>
    <el-pagination v-model:current-page="pageNo" v-model:page-size="pageSize" :page-sizes="[5, 7, 9, 11]"
      :background="true" layout="prev, pager, next, jumper,->,sizes,total" :total="total" @current-change="getHasUser"
      @size-change="handler" />
  </el-card>
  <!-- 抽屉结构:完成添加新的用户账号更新已有的账号信息 -->
  <el-drawer v-model="drawer">
    <template #header>
      <h4>{{ userParams.id ? '更新用户' : '添加用户' }}</h4>
    </template>
    <template #default>
      <el-form :model="userParams" :rules="rules" ref="formRef">
        <el-form-item label="用户姓名" prop="username">
          <el-input placeholder="请您输入用户姓名" v-model="userParams.username"></el-input>
        </el-form-item>
        <el-form-item label="用户昵称" prop="name">
          <el-input placeholder="请您输入用户昵称" v-model="userParams.name"></el-input>
        </el-form-item>
        <el-form-item label="用户密码" prop="password" v-if="!userParams.id">
          <el-input placeholder="请您输入用户密码" v-model="userParams.password"></el-input>
        </el-form-item>
      </el-form>
    </template>
    <template #footer>
      <div style="flex: auto">
        <el-button @click="cancel">取消</el-button>
        <el-button type="primary" @click="save">确认</el-button>
      </div>
    </template>
  </el-drawer>
  <!-- 抽屉结构:用于某个已有账号进行职位分配 -->
  <el-drawer v-model="drawer1">
    <template #header>
      <h4>分配角色用户</h4>
    </template>
    <template #default>
      <el-form>
        <el-form-item label="用户姓名">
          <el-input v-model="userParams.username" :disabled="true"></el-input>
        </el-form-item>
        <el-form-item label="角色列表">
          <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">全选</el-checkbox>
          <!-- 显示职位的复选框 -->
          <el-checkbox-group v-model="userRole" @change="handleCheckedCitiesChange">
            <el-checkbox v-for="(role, index) in allRole" :key="index" :label="role">{{ role.roleName }}</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
      </el-form>
 
    </template>
    <template #footer>
      <div style="flex: auto">
        <el-button @click="drawer1 = false">取消</el-button>
        <el-button type="primary" @click="confirmClick">确认</el-button>
      </div>
    </template>
  </el-drawer>
</template>
 
<script setup lang="ts">
import useLayOutSettingStore from '@/store/modules/setting'
import { reqAddOrUpdateUser, reqAllRole, reqRemoveUser, reqSelectUser, reqSetUserRole, reqUserInfo } from '@/api/acl/user';
import type { AllRole, AllRoleResponseData, Records, SetRoleData, User, UserResponseData } from '@/api/acl/user/type'
import { ElMessage } from 'element-plus';
import { nextTick, onMounted, reactive, ref } from 'vue';
// 默认页码
let pageNo = ref<number>(1)
// 一页展示几条数据
let pageSize = ref<number>(10)
// 用户总个数
let total = ref<number>(0)
// 存储全部用户的数组
let userArr = ref<Records>([])
// 定义响应式数据控制抽屉的显示与隐藏
let drawer = ref<boolean>(false)
// 控制分配角色抽屉显示与隐藏
let drawer1 = ref<boolean>(false)
// 收集用户信息的响应式数据
let userParams = reactive<User>({
  username: '',
  name: '',
  password: ''
})
// 获取dorm组件实例
let formRef = ref()
//定义响应式数据:收集用户输入进来的关键字
let keyword = ref<string>('')
//获取模板setting仓库
let settingStore = useLayOutSettingStore()
// 组件挂载完毕
onMounted(() => {
  getHasUser()
})
// 获取全部已有用户信息
const getHasUser = async (pager = 1) => {
  // 收集当前页码
  pageNo.value = pager
  let result: UserResponseData = await reqUserInfo(pageNo.value, pageSize.value, keyword.value)
  if (result.code === 200) {
    total.value = result.data.total
    userArr.value = result.data.records
  }
}
// 分页器下拉菜单的自定义事件的回调
const handler = () => {
  getHasUser()
}
// 添加用户按钮的回调
const addUser = () => {
  // 抽屉显示出来
  drawer.value = true
  // 清空数据
  Object.assign(userParams, {
    id: 0,
    username: '',
    name: '',
    password: ''
  })
  // 清除上一次的错误提示信息
  nextTick(() => {
    formRef.value.clearValidate('username')
    formRef.value.clearValidate('name')
    formRef.value.clearValidate('password')
  })
}
// 更新已有的用户按钮的回调
// row:即为已有用户的账号信息
const updateUser = (row: User) => {
  // 抽屉显示出来
  drawer.value = true
  // 存储收集已有的账号xinx
  Object.assign(userParams, row)
  // 清除上一次的错误提示信息
  nextTick(() => {
    formRef.value.clearValidate('username')
    formRef.value.clearValidate('name')
  })
}
 
// 保存按钮的回调
const save = async () => {
  // 点击保存按钮的时候,务必需要保证表单全部符合条件再去发请求
  await formRef.value.validate()
  // 保存按钮:添加新的用户|更新已有的用户账号信息
  let result: any = await reqAddOrUpdateUser(userParams)
  if (result.code === 200) {
    // 关闭抽屉
    drawer.value = false
    // 提示信息
    ElMessage({
      type: 'success',
      message: userParams.id ? '更新成功' : '添加成功'
    })
    // 获取最新的全部账号信息
    // getHasUser(userParams.id ? pageNo.value : 1)
    // 浏览器自动刷新一次(为了解决修改到当前登录用户信息需要重新登录的问题)
    window.location.reload()
  } else {
    // 关闭抽屉
    drawer.value = false
    // 提示信息
    ElMessage({
      type: 'error',
      message: userParams.id ? '更新失败' : '添加失败'
    })
  }
}
// 取消按钮的回调
const cancel = () => {
  // 关闭抽屉
  drawer.value = false
}
// 校验用户名字回调函数
const validatorUsername = (rule: any, value: any, callBack: any) => {
  // 用户名字|昵称,长度至少五位
  if (value.trim().length >= 5) {
    callBack()
  } else {
    callBack(new Error('用户名字至少五位'))
  }
}
// 校验用户昵称回调函数
const validatorName = (rule: any, value: any, callBack: any) => {
  // 用户名字|昵称,长度至少五位
  if (value.trim().length >= 5) {
    callBack()
  } else {
    callBack(new Error('用户昵称至少五位'))
  }
}
// 校验用户名字回调函数
const validatorPassword = (rule: any, value: any, callBack: any) => {
  // 用户名字|昵称,长度至少六位
  if (value.trim().length >= 6) {
    callBack()
  } else {
    callBack(new Error('用户密码至少六位'))
  }
}
// 表单校验的规则对象
const rules = {
  // 用户名字
  username: [{ required: true, trigger: 'blur', validator: validatorUsername }],
  // 用户昵称
  name: [{ required: true, trigger: 'blur', validator: validatorName }],
  // 用户密码
  password: [{ required: true, trigger: 'blur', validator: validatorPassword }]
}
 
// 分配角色按钮的回调
const setRole = async (row: User) => {
  // 存储已有的用户信息
  Object.assign(userParams, row)
  //获取全部的职位的数据与当前用户已有的职位的数据
  let result: AllRoleResponseData = await reqAllRole((userParams.id as number))
  if (result.code === 200) {
    //存储全部的职位
    allRole.value = result.data.allRolesList
    //存储当前用户已有的职位  
    userRole.value = result.data.assignRoles
    // 抽屉显示出来
    drawer1.value = true
  }
}
 
//收集顶部复选框全选数据
const checkAll = ref<boolean>(false)
//控制顶部全选复选框不确定的样式
const isIndeterminate = ref<boolean>(true)
//存储全部职位的数据
let allRole = ref<AllRole>([])
//当前用户已有的职位
let userRole = ref<AllRole>([])
//顶部的全部复选框的change事件
const handleCheckAllChange = (val: boolean) => {
  //val:true(全选)|false(没有全选)
  userRole.value = val ? allRole.value : []
  //不确定的样式(确定样式)
  isIndeterminate.value = false
}
//顶部全部的复选框的change事件
const handleCheckedCitiesChange = (value: string[]) => {
  //顶部复选框的勾选数据
  //代表:勾选上的项目个数与全部的职位个数相等,顶部的复选框勾选上
  checkAll.value = value.length === allRole.value.length
  //不确定的样式
  isIndeterminate.value = value.length !== allRole.value.length
}
// 确定按钮的回调(分配职位)
const confirmClick = async () => {
  // 收集参数
  let data: SetRoleData = {
    userId: (userParams.id as number),
    roleIdList: userRole.value.map(item => {
      return (item.id as number)
    })
  }
  // 分配用户的职位
  let result: any = await reqSetUserRole(data)
  if (result.code === 200) {
    // 提示信息
    ElMessage({
      type: 'success',
      message: '分配职务成功'
    })
    // 关闭抽屉
    drawer1.value = false
    // 获取更新完毕用户的信息,更新完毕留在当前页
    getHasUser(pageNo.value)
  }
}
 
// 删除某一个用户
const deleteUser = async (userId: number) => {
  let result: any = await reqRemoveUser(userId)
  if (result.code === 200) {
    ElMessage({
      type: 'success',
      message: '删除成功'
    })
    getHasUser(userArr.value.length > 1 ? pageNo.value : pageNo.value - 1)
  }
}
 
//准备一个数组存储批量删除的用户的ID
let selectIdArr = ref<User[]>([])
//table复选框勾选的时候会触发的事件
const selectChange = (value: any) => {
  selectIdArr.value = value
}
 
// 批量删除按钮的回调
const deleteSelectUser = async () => {
  //整理批量删除的参数
  let idsList: any = selectIdArr.value.map(item => {
    return item.id
  })
  //批量删除的请求
  let result: any = await reqSelectUser(idsList)
  if (result.code === 200) {
    ElMessage({
      type: 'success',
      message: '删除成功'
    })
    getHasUser(userArr.value.length > 1 ? pageNo.value : pageNo.value - 1)
  }
}
 
// 搜索按钮的回调
const search = () => {
  //根据关键字获取相应的用户数据
  getHasUser()
  //清空关键字
  keyword.value = ''
}
//重置按钮
const reset = () => {
  settingStore.refresh = !settingStore.refresh
}
</script>
 
<style scoped>
.form {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

 二、角色管理 

1.静态搭建

静态搭建这一部分和用户管理的极为相似这里就不细写。

2.业务实现流程

2.1数据的展示

2.1角色管理模块数据的展示
(1)定义API接口,方法,数据类型
(2)挂载时和切换页码时发送请求展示已有数据,引入onMounted和请求方法
(3)获取数据,存储全部已有的职位
(4)展示数据
分页器
:total="total" @current-change="getHasRole" @size-change="sizeChange",
table用:data="roleArr"
其他的用prop展示

// 组件挂载完毕
onMounted(() => {
    // 获取角色请求
    getHasRole()
})
// 获取全部已有的角色信息的方法|分页器当前页码发生变化的回调
const getHasRole = async (pager = 1) => {
    // 修改当前页码
    pageNo.value = pager
    let result: RoleResponseData = await reqRoleInfo(
        pageNo.value,
        pageSize.value,
        keyword.value,
    )
    if (result.code === 200) {
        total.value = result.data.total
        roleArr.value = result.data.records
    }
}
// 分页器下拉菜单的自定义事件的回调
const sizeChange = () => {
    getHasRole()
}

(5)搜索重置功能
先收集到输入的数据keyword,搜索按钮禁用效果,绑定search方法和重置reset方法

// 搜索按钮的回调
const search = () => {
    //根据关键字获取相应的角色数据
    getHasRole()
    //清空关键字
    keyword.value = ''
}

//获取模板setting仓库
let settingStore = useLayOutSettingStore()
// 重置按钮的回调
const reset = () => {
    settingStore.refresh = !settingStore.refresh
}

 2.2添加和更新角色

1.静态搭建
(1)添加新增角色更新角色的 地址和方法(两者可以封装到一个函数方法里面,唯一的区别就是有无id,新增没有id,更新有id)
(2)dialog对话框
(3)控制对话框显示与隐藏。给添加和编辑按钮添加事件addRole,updateRole(row)
(4)取消按钮 @click="dialogVisible = false"

<!-- 添加角色与更新已有角色的结构:对话框 -->
    <el-dialog :title="roleParams.id ? '更新' : '添加'" v-model="dialogVisible">
        <el-form :model="roleParams" :rules="rules" ref="form">
            <el-form-item label="角色名称" prop="roleName">
                <el-input placeholder="请填写角色名称" v-model="roleParams.roleName"></el-input>
            </el-form-item>
        </el-form>
        <template #footer>
            <el-button size="default" @click="dialogVisible = false">取消</el-button>
            <el-button type="primary" size="default" @click="save">确认</el-button>
        </template>
    </el-dialog>

2.完成添加职位

收集到新增的职位数据》表单校验》判断》再次获取全部已有数据》清空数据》清空错误提示》将对话框隐藏(记得更新返回的是当前页还是添加上一页)
(1)收集新增职位数据

// 收集新增岗位数据
let roleParams = reactive<RoleData>({
    roleName: '',
})

(2)自定义表单校验

// 自定义校验规则的回调
const validatorRoleName = (rule: any, value: any, callBack: any) => {
    // 角色名称,长度至少五位
    if (value.trim().length >= 2) {
        callBack()
    } else {
        callBack(new Error('角色名称至少两位'))
    }
}
// 角色校验规则
const rules = {
    roleName: [{ required: true, trigger: 'blur', validator: validatorRoleName }],
}

(3)确定按钮save的回调

// 确定按钮的回调
const save = async () => {
    // 表单校验结果,结果通过再发请求,结果没通过不应该发请求
    await form.value.validate()
    // 添加职位|更新角色的请求
    let result: any = await reqAddOrUpdateRole(roleParams)
    if (result.code === 200) {
        // 提示信息
        ElMessage({
            type: 'success',
            message: roleParams.id ? '添加成功' : '更新成功',
        })
        // 再次获取全部已有角色
        getHasRole(roleArr.value.length > 1 ? pageNo.value : pageNo.value - 1)
    } else {
        // 提示信息
        ElMessage({
            type: 'error',
            message: roleParams.id ? '添加失败' : '更新失败',
        })
    }
    // 对话框隐藏
    dialogVisible.value = false
}


(4)添加按钮的回调

// 添加角色按钮的回调
const addRole = () => {
    // 对话框显示出来
    dialogVisible.value = true
    // 清空数据
    Object.assign(roleParams, {
        roleName: '',
        id: 0,
    })
    // 清空上一次表单校验错误提示
    nextTick(() => {
        form.value.clearValidate('roleName')
    })
}

3.更新(修改)按钮完成

updateRole回调

// 更新角色按钮的回调
const updateRole = (row: RoleData) => {
    // 对话框显示出来
    dialogVisible.value = true
    // 存储已有角色---带有id的
    Object.assign(roleParams, row)
    // 清空上一次表单校验错误提示
    nextTick(() => {
        form.value.clearValidate('roleName')
    })
}

 2.3分配权限

(1)静态搭建:抽屉组件drawer》tree树形控件

<!-- 抽屉组件:分配角色的菜单权限与按钮权限 -->
    <el-drawer v-model="drawer">
        <template #header>
            <h4>分配菜单与按钮的权限</h4>
        </template>
        <template #default>
            <el-tree ref="tree" :data="menuArr" show-checkbox node-key="id" default-expand-all
                :default-checked-keys="selectArr" :props="defaultProps" />
        </template>
        <template #footer>
            <div style="flex: auto">
                <el-button @click="drawer = false">取消</el-button>
                <el-button type="primary" @click="handler">确认</el-button>
            </div>
        </template>
    </el-drawer>

(2)书写API接口和方法,定义数据类型
(3)分配权限按钮的回调setPermission

//分配权限按钮的回调
//已有的职位的数据
const setPermission = async (row: RoleData) => {
    //抽屉显示出来
    drawer.value = true
    //收集当前要分类权限的职位的数据
    Object.assign(roleParams, row)
    //根据职位获取权限的数据
    let result: MenuResponseData = await reqAllMenuList(roleParams.id as number)
    if (result.code === 200) {
        menuArr.value = result.data
        selectArr.value = filterSelectArr(menuArr.value, [])
    }
}

const defaultProps = {
    children: 'children',
    label: 'name',
}

(4)展示菜单权限数据和按钮数据::data="menuArr"
(5)勾选用户已有的权限
准备数组存储四级select为true的id;


//准备一个数组:数组用于存储勾选的节点的ID(四级的)
let selectArr = ref<number[]>([])

 过滤四级的id.(递归)

const filterSelectArr = (allData: any, initArr: any) => {
    allData.forEach((item: any) => {
        if (item.select && item.level === 4) {
            initArr.push(item.id)
        }
        if (item.children && item.children.length > 0) {
            filterSelectArr(item.children, initArr)
        }
    })
    return initArr
}

(6)抽屉确定按钮的回调

携带职位的id,选中节点的id,半选的id》下发权限》判断》页面刷新window

//抽屉确定按钮的回调
const handler = async () => {
    //职位的ID
    const roleId = roleParams.id as number
    //选中节点的ID
    let arr = tree.value.getCheckedKeys()
    //半选的ID
    let arr1 = tree.value.getHalfCheckedKeys()
    let permissionId = arr.concat(arr1)
    //下发权限
    let result: any = await reqSetPermission(roleId, permissionId)
    if (result.code === 200) {
        //抽屉关闭
        drawer.value = false
        //提示信息
        ElMessage({
            type: 'success',
            message: '分配权限成功',
        })
        //页面刷新
        window.location.reload()
    }
}

2.3删除业务

(1)编写API方法,定义数据类型
(2)气泡确认框

<el-popconfirm :title="`你确定要删除${row.roleName}?`" width="260px" @confirm="removeRole(row.id)">
                        <template #reference>
                            <el-button type="primary" size="small" icon="Delete">
                                删除
                            </el-button>
                        </template>
                    </el-popconfirm>

(3)删除的回调 removeRole
 

//删除已有的职位
const removeRole = async (id: number) => {
    let result: any = await reqRemoveRole(id)
    if (result.code === 200) {
        ElMessage({
            type: 'success',
            message: '删除成功',
        })
        getHasRole(roleArr.value.length > 1 ? pageNo.value : pageNo.value - 1)
    }
}

3.代码 

src/api/acl/role/index.ts

// 角色管理模块的接口
import request from '@/utils/request'
import type { RoleResponseData, RoleData, MenuResponseData } from './type'
// 枚举地址
enum API {
  // 获取全部角色的接口
  ALLROLE_URL = '/admin/acl/role/',
  // 新增角色的接口地址
  ADDROLE_URL = '/admin/acl/role/save',
  // 更新已有的角色
  UPDATEROLE_URL = '/admin/acl/role/update',
  //获取全部的菜单与按钮的数据
  ALLPERMISSION = '/admin/acl/permission/toAssign/',
  //给相应的职位分配权限
  SETPERMISTION_URL = '/admin/acl/permission/doAssign/?',
  //删除已有的职位
  REMOVEROLE_URL = '/admin/acl/role/remove/',
}
 
// 获取全部的角色
export const reqRoleInfo = (page: number, limit: number, roleName: string) =>
  request.get<any, RoleResponseData>(
    API.ALLROLE_URL + `${page}/${limit}/?roleName=${roleName}`,
  )
// 添加与更新角色接口
export const reqAddOrUpdateRole = (data: RoleData) => {
  if (data.id) {
    return request.put<any, any>(API.UPDATEROLE_URL, data)
  } else {
    return request.post<any, any>(API.ADDROLE_URL, data)
  }
}
//获取全部菜单与按钮权限数据
export const reqAllMenuList = (roleId: number) =>
  request.get<any, MenuResponseData>(API.ALLPERMISSION + roleId)
//给相应的职位下发权限
export const reqSetPermission = (roleId: number, permissionId: number[]) =>
  request.post(
    API.SETPERMISTION_URL + `roleId=${roleId}&permissionId=${permissionId}`,
  )
//删除已有的职位
export const reqRemoveRole = (roleId: number) =>
  request.delete<any, any>(API.REMOVEROLE_URL + roleId)

 src/api/acl/role/type.ts

export interface ResponseData {
    code: number
    message: string
    ok: boolean
  }
  //职位数据类型
  export interface RoleData {
    id?: number
    createTime?: string
    updateTime?: string
    roleName: string
    remark?: null
  }
  
  //全部职位的数组的ts类型
  export type Records = RoleData[]
  //全部职位数据的相应的ts类型
  export interface RoleResponseData extends ResponseData {
    data: {
      records: Records
      total: number
      size: number
      current: number
      orders: []
      optimizeCountSql: boolean
      hitCount: boolean
      countId: null
      maxLimit: null
      searchCount: boolean
      pages: number
    }
  }
  
  //菜单与按钮数据的ts类型
  export interface MunuData {
    id: number
    createTime: string
    updateTime: string
    pid: number
    name: string
    code: string
    toCode: string
    type: number
    status: null
    level: number
    children?: MenuList
    select: boolean
  }
  export type MenuList = MunuData[]
  
  //菜单权限与按钮权限数据的ts类型
  export interface MenuResponseData extends ResponseData {
    data: MenuList
  }

src/views/acl/role/index.vue   

PS:filterSelectArr方法只需要过滤最深一个层级的id(即第四级),因为只要判断最深一级是否有勾选,如果有一个或多个勾选了,则它相关的所有上级必定是勾选状态;如果一个也没勾选,则它相关的所有上级必定也是未勾选状态。

<template>
  <el-card style="height: 80px">
    <el-form :inline="true" class="form">
      <el-form-item label="角色名称">
        <el-input placeholder="角色名称" v-model="keyword"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="search">搜索</el-button>
        <el-button type="primary" plain="primary" @click="reset">
          重置
        </el-button>
      </el-form-item>
    </el-form>
  </el-card>
  <el-card style="margin: 10px 0">
    <el-button type="primary" size="default" icon="Plus" @click="addRole">
      添加角色
    </el-button>
    <el-table border style="margin: 10px 0" :data="roleArr">
      <el-table-column type="index" label="#" align="center"></el-table-column>
      <el-table-column label="id" align="center" prop="id"></el-table-column>
      <el-table-column
        label="角色名称"
        align="center"
        show-overflow-tooltip
        prop="roleName"
      ></el-table-column>
      <el-table-column
        label="创建时间"
        align="center"
        show-overflow-tooltip
        prop="createTime"
      ></el-table-column>
      <el-table-column
        label="更新时间"
        align="center"
        show-overflow-tooltip
        prop="updateTime"
      ></el-table-column>
      <el-table-column label="操作" width="300px" align="center">
        <template #="{ row, $index }">
          <el-button
            type="primary"
            size="small"
            icon="User"
            @click="setPermission(row)"
          >
            分配权限
          </el-button>
          <el-button
            type="primary"
            size="small"
            icon="Edit"
            @click="updateRole(row)"
          >
            编辑
          </el-button>
          <el-popconfirm
            :title="`你确定要删除${row.roleName}?`"
            width="260px"
            @confirm="removeRole(row.id)"
          >
            <template #reference>
              <el-button type="primary" size="small" icon="Delete">
                删除
              </el-button>
            </template>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>
    <el-pagination
      v-model:current-page="pageNo"
      v-model:page-size="pageSize"
      :page-sizes="[10, 20, 30, 40]"
      :background="true"
      layout="prev, pager, next, jumper,->,sizes,total"
      :total="total"
      @current-change="getHasRole"
      @size-change="sizeChange"
    />
  </el-card>
  <!-- 添加角色与更新已有角色的结构:对话框 -->
  <el-dialog :title="roleParams.id ? '更新' : '添加'" v-model="dialogVisible">
    <el-form :model="roleParams" :rules="rules" ref="form">
      <el-form-item label="角色名称" prop="roleName">
        <el-input
          placeholder="请填写角色名称"
          v-model="roleParams.roleName"
        ></el-input>
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button size="default" @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" size="default" @click="save">确认</el-button>
    </template>
  </el-dialog>
  <!-- 抽屉组件:分配角色的菜单权限与按钮权限 -->
  <el-drawer v-model="drawer">
    <template #header>
      <h4>分配菜单与按钮的权限</h4>
    </template>
    <template #default>
      <el-tree
        ref="tree"
        :data="menuArr"
        show-checkbox
        node-key="id"
        default-expand-all
        :default-checked-keys="selectArr"
        :props="defaultProps"
      />
    </template>
    <template #footer>
      <div style="flex: auto">
        <el-button @click="drawer = false">取消</el-button>
        <el-button type="primary" @click="handler">确认</el-button>
      </div>
    </template>
  </el-drawer>
</template>
 
<script setup lang="ts">
import useLayOutSettingStore from '@/store/modules/setting'
import {
  reqAddOrUpdateRole,
  reqAllMenuList,
  reqRemoveRole,
  reqRoleInfo,
  reqSetPermission,
} from '@/api/acl/role'
import type {
  RoleResponseData,
  Records,
  RoleData,
  MenuResponseData,
  MenuList,
} from '@/api/acl/role/type'
import { nextTick, onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
// 当前页码
let pageNo = ref<number>(1)
// 一夜展示几条数据
let pageSize = ref<number>(10)
// 角色总个数
let total = ref<number>(0)
// 存储全部角色的数组
let roleArr = ref<Records>([])
// 搜索角色关键字
let keyword = ref<string>('')
// 控制对话框显示与隐藏
let dialogVisible = ref<boolean>(false)
// 收集新增岗位数据
let roleParams = reactive<RoleData>({
  roleName: '',
})
// 控制抽屉显示与隐藏
let drawer = ref<boolean>(false)
// 获取form组件实例
let form = ref<any>()
//定义数组存储用户权限的数据
let menuArr = ref<MenuList>([])
//准备一个数组:数组用于存储勾选的节点的ID(四级的)
let selectArr = ref<number[]>([])
//获取tree组件实例
let tree = ref<any>()
// 组件挂载完毕
onMounted(() => {
  // 获取角色请求
  getHasRole()
})
// 获取全部已有的角色信息的方法|分页器当前页码发生变化的回调
const getHasRole = async (pager = 1) => {
  // 修改当前页码
  pageNo.value = pager
  let result: RoleResponseData = await reqRoleInfo(
    pageNo.value,
    pageSize.value,
    keyword.value,
  )
  if (result.code === 200) {
    total.value = result.data.total
    roleArr.value = result.data.records
  }
}
// 分页器下拉菜单的自定义事件的回调
const sizeChange = () => {
  getHasRole()
}
// 搜索按钮的回调
const search = () => {
  //根据关键字获取相应的角色数据
  getHasRole()
  //清空关键字
  keyword.value = ''
}
 
//获取模板setting仓库
let settingStore = useLayOutSettingStore()
// 重置按钮的回调
const reset = () => {
  settingStore.refresh = !settingStore.refresh
}
// 添加角色按钮的回调
const addRole = () => {
  // 对话框显示出来
  dialogVisible.value = true
  // 清空数据
  Object.assign(roleParams, {
    roleName: '',
    id: 0,
  })
  // 清空上一次表单校验错误提示
  nextTick(() => {
    form.value.clearValidate('roleName')
  })
}
// 更新角色按钮的回调
const updateRole = (row: RoleData) => {
  // 对话框显示出来
  dialogVisible.value = true
  // 存储已有角色---带有id的
  Object.assign(roleParams, row)
  // 清空上一次表单校验错误提示
  nextTick(() => {
    form.value.clearValidate('roleName')
  })
}
 
// 自定义校验规则的回调
const validatorRoleName = (rule: any, value: any, callBack: any) => {
  // 角色名称,长度至少五位
  if (value.trim().length >= 2) {
    callBack()
  } else {
    callBack(new Error('角色名称至少两位'))
  }
}
// 角色校验规则
const rules = {
  roleName: [{ required: true, trigger: 'blur', validator: validatorRoleName }],
}
 
// 确定按钮的回调
const save = async () => {
  // 表单校验结果,结果通过再发请求,结果没通过不应该发请求
  await form.value.validate()
  // 添加职位|更新角色的请求
  let result: any = await reqAddOrUpdateRole(roleParams)
  if (result.code === 200) {
    // 提示信息
    ElMessage({
      type: 'success',
      message: roleParams.id ? '添加成功' : '更新成功',
    })
    // 再次获取全部已有角色
    getHasRole(roleArr.value.length > 1 ? pageNo.value : pageNo.value - 1)
  } else {
    // 提示信息
    ElMessage({
      type: 'error',
      message: roleParams.id ? '添加失败' : '更新失败',
    })
  }
  // 对话框隐藏
  dialogVisible.value = false
}
 
//分配权限按钮的回调
//已有的职位的数据
const setPermission = async (row: RoleData) => {
  //抽屉显示出来
  drawer.value = true
  //收集当前要分类权限的职位的数据
  Object.assign(roleParams, row)
  //根据职位获取权限的数据
  let result: MenuResponseData = await reqAllMenuList(roleParams.id as number)
  if (result.code === 200) {
    menuArr.value = result.data
    selectArr.value = filterSelectArr(menuArr.value, [])
  }
}
 
const defaultProps = {
  children: 'children',
  label: 'name',
}
 
const filterSelectArr = (allData: any, initArr: any) => {
  allData.forEach((item: any) => {
    if (item.select && item.level === 4) {
      initArr.push(item.id)
    }
    if (item.children && item.children.length > 0) {
      filterSelectArr(item.children, initArr)
    }
  })
  return initArr
}
 
//抽屉确定按钮的回调
const handler = async () => {
  //职位的ID
  const roleId = roleParams.id as number
  //选中节点的ID
  let arr = tree.value.getCheckedKeys()
  //半选的ID
  let arr1 = tree.value.getHalfCheckedKeys()
  let permissionId = arr.concat(arr1)
  //下发权限
  let result: any = await reqSetPermission(roleId, permissionId)
  if (result.code === 200) {
    //抽屉关闭
    drawer.value = false
    //提示信息
    ElMessage({
      type: 'success',
      message: '分配权限成功',
    })
    //页面刷新
    window.location.reload()
  }
}
//删除已有的职位
const removeRole = async (id: number) => {
  let result: any = await reqRemoveRole(id)
  if (result.code === 200) {
    ElMessage({
      type: 'success',
      message: '删除成功',
    })
    getHasRole(roleArr.value.length > 1 ? pageNo.value : pageNo.value - 1)
  }
}
</script>
 
<style scoped>
.form {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

三、菜单管理 

1.展示已有菜单数据

展示已有菜单数据
(1)编写API接口,方法,定义数据类型
(2)静态搭建:table组件

<el-table :data="permisstinArr" style="width: 100%; margin-bottom: 20px" row-key="id" border>
        <el-table-column label="名称" prop="name"></el-table-column>
        <el-table-column label="权限值" prop="code"></el-table-column>
        <el-table-column label="修改时间" prop="updateTime"></el-table-column>
        <el-table-column label="操作">
            <!-- row:即为已有菜单对象|按钮对象的数据 -->
            <template #="{ row, $index }">
                <el-button @click="addPermission(row)" type="primary" size="small" :disabled="row.level === 4">
                    {{ row.level === 3 ? '添加功能' : '添加菜单' }}
                </el-button>
                <el-button @click="updatePermission(row)" type="primary" size="small" :disabled="row.level === 1">
                    编辑
                </el-button>
                <el-popconfirm :title="`你确定要删除${row.name}?`" width="260px" @confirm="removeMenu(row.id)">
                    <template #reference>
                        <el-button type="primary" size="small" :disabled="row.level === 1">
                            删除
                        </el-button>
                    </template>
                </el-popconfirm>
            </template>
        </el-table-column>
    </el-table>

(3)组件挂载完毕获取菜单数据
(4)获取菜单数据
获取》存储》判断

// 组件挂载完毕
onMounted(() => {
    getHasPermission()
})

//  获取菜单数据的方法
const getHasPermission = async () => {
    let result: PermissionResponseData = await reqAllPermission()
    if (result.code === 200) {
        permisstinArr.value = result.data
    }
}

(5)展示数据
el-table中:data="permisstinArr"
el-table-column中:prop
(6)判断按钮是否禁用
一级禁用编辑和删除按钮
四级禁用添加菜单按钮

 2.添加与更新菜单

(1)静态搭建
el-dialog》

<!-- 对话框组件:添加或者更新已有的菜单的数据结构 -->
    <el-dialog v-model="dialogVisible" :title="menuData.id ? '更新菜单' : '添加菜单'" width="30%">
        <!-- 表单组件:收集新增与已有菜单的数据 -->
        <el-form>
            <el-form-item label="名称">
                <el-input placeholder="请输入菜单名称" v-model="menuData.name"></el-input>
            </el-form-item>
            <el-form-item label="权限">
                <el-input placeholder="请输入权限值" v-model="menuData.code"></el-input>
            </el-form-item>
        </el-form>
        <template #footer>
            <span class="dialog-footer">
                <el-button @click="dialogVisible = false">取消</el-button>
                <el-button type="primary" @click="save">确认</el-button>
            </span>
        </template>
    </el-dialog>

(2)控制对话框的显示与隐藏
// 控制对话框的显示与隐藏
let dialogVisible = ref<boolean>(false)
(3)添加菜单按钮的回调addPermission(row)

// 添加菜单按钮的回调
const addPermission = (row: Permission) => {
    // 清空数据
    Object.assign(menuData, {
        id: 0,
        code: '',
        level: 0,
        name: '',
        pid: 0,
    })
    // 对话框显示出来
    dialogVisible.value = true
    // 收集新增菜单的level数值
    menuData.level = row.level + 1
    // 给谁新增子菜单
    menuData.pid = row.id as number
}

(4)编辑菜单按钮的回调updatePermission(row)

// 编辑已有菜单
const updatePermission = (row: Permission) => {
    // 对话框显示出来
    dialogVisible.value = true
    //点击修改按钮:收集已有的菜单的数据进行更新
    Object.assign(menuData, row)
}

(5)确定按钮的回调save

// 确认按钮的回调
const save = async () => {
    let result: any = await reqAddOrUpdateMenu(menuData)
    if (result.code === 200) {
        //对话框隐藏
        dialogVisible.value = false
        // 信息提示
        ElMessage({
            type: 'success',
            message: menuData.id ? '更新成功' : '添加成功',
        })
        //再次获取全部最新的菜单的数据
        getHasPermission()
    }
}

(6)取消按钮
@click="dialogVisible = false"
(7)记得定义API接口、方法以及数据类型

3.删除已有菜单

(1)编写API接口、方法,定义数据类型
(2)删除按钮的回调。
静态》el-popconfirm

<el-popconfirm :title="`你确定要删除${row.name}?`" width="260px" @confirm="removeMenu(row.id)">
                    <template #reference>
                        <el-button type="primary" size="small" :disabled="row.level === 1">
                            删除
                        </el-button>
                    </template>
                </el-popconfirm>

发送请求》再次更新数据

// 删除按钮的回调
const removeMenu = async (id: number) => {
    let result: any = await reqRemoveMenu(id)
    if (result.code === 200) {
        ElMessage({
            type: 'success',
            message: '删除成功',
        })
        getHasPermission()
    }
}

4.代码 

src/api/acl/menu/index.ts

import request from '@/utils/request'
import type { PermissionResponseData, MenuParams } from './type'
//枚举地址
enum API {
  //获取全部菜单与按钮的标识数据
  ALLPERMISSION_URL = '/admin/acl/permission',
  //给某一级菜单新增一个子菜单
  ADDMENU_URL = '/admin/acl/permission/save',
  //更新某一个已有的菜单
  UPDATE_URL = '/admin/acl/permission/update',
  //删除已有的菜单
  DELETEMENU_URL = '/admin/acl/permission/remove/',
}
//获取菜单数据
export const reqAllPermission = () =>
  request.get<any, PermissionResponseData>(API.ALLPERMISSION_URL)
//添加与更新菜单的方法
export const reqAddOrUpdateMenu = (data: MenuParams) => {
  if (data.id) {
    return request.put<any, any>(API.UPDATE_URL, data)
  } else {
    return request.post<any, any>(API.ADDMENU_URL, data)
  }
}
 
//删除某一个已有的菜单
export const reqRemoveMenu = (id: number) =>
  request.delete<any, any>(API.DELETEMENU_URL + id)

  src/api/acl/menu/type.ts

//数据类型定义
export interface ResponseData {
  code: number
  message: string
  ok: boolean
}
//菜单数据与按钮数据的ts类型
export interface Permission {
  id?: number
  createTime: string
  updateTime: string
  pid: number
  name: string
  code: null
  toCode: null
  type: number
  status: null
  level: number
  children?: PermissionList
  select: boolean
}
export type PermissionList = Permission[]
//菜单接口返回的数据类型
export interface PermissionResponseData extends ResponseData {
  data: PermissionList
}
 
//添加与修改菜单携带的参数的ts类型
export interface MenuParams {
  id?: number //ID
  code: string //权限数值
  level: number //几级菜单
  name: string //菜单的名字
  pid: number //菜单的ID
}

src/views/acl/permission/index.vue   

<template>
  <el-table
    :data="permisstinArr"
    style="width: 100%; margin-bottom: 20px"
    row-key="id"
    border
  >
    <el-table-column label="名称" prop="name"></el-table-column>
    <el-table-column label="权限值" prop="code"></el-table-column>
    <el-table-column label="修改时间" prop="updateTime"></el-table-column>
    <el-table-column label="操作">
      <!-- row:即为已有菜单对象|按钮对象的数据 -->
      <template #="{ row, $inde }">
        <el-button
          @click="addPermission(row)"
          type="primary"
          size="small"
          :disabled="row.level === 4"
        >
          {{ row.level === 3 ? '添加功能' : '添加菜单' }}
        </el-button>
        <el-button
          @click="updatePermission(row)"
          type="primary"
          size="small"
          :disabled="row.level === 1"
        >
          编辑
        </el-button>
        <el-popconfirm
          :title="`你确定要删除${row.name}?`"
          width="260px"
          @confirm="removeMenu(row.id)"
        >
          <template #reference>
            <el-button type="primary" size="small" :disabled="row.level === 1">
              删除
            </el-button>
          </template>
        </el-popconfirm>
      </template>
    </el-table-column>
  </el-table>
  <!-- 对话框组件:添加或者更新已有的菜单的数据结构 -->
  <el-dialog
    v-model="dialogVisible"
    :title="menuData.id ? '更新菜单' : '添加菜单'"
    width="30%"
  >
    <!-- 表单组件:收集新增与已有菜单的数据 -->
    <el-form>
      <el-form-item label="名称">
        <el-input
          placeholder="请输入菜单名称"
          v-model="menuData.name"
        ></el-input>
      </el-form-item>
      <el-form-item label="权限">
        <el-input placeholder="请输入权限值" v-model="menuData.code"></el-input>
      </el-form-item>
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="save">确认</el-button>
      </span>
    </template>
  </el-dialog>
</template>
 
<script setup lang="ts">
import {
  reqAddOrUpdateMenu,
  reqAllPermission,
  reqRemoveMenu,
} from '@/api/acl/menu'
import {
  MenuParams,
  Permission,
  PermissionList,
  PermissionResponseData,
} from '@/api/acl/menu/type'
import { ElMessage } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
let permisstinArr = ref<PermissionList>([])
// 控制对话框的显示与隐藏
let dialogVisible = ref<boolean>(false)
// 携带的参数
let menuData = reactive<MenuParams>({
  code: '',
  level: 0,
  name: '',
  pid: 0,
})
// 组件挂载完毕
onMounted(() => {
  getHasPermission()
})
 
//  获取菜单数据的方法
const getHasPermission = async () => {
  let result: PermissionResponseData = await reqAllPermission()
  if (result.code === 200) {
    permisstinArr.value = result.data
  }
}
 
// 添加菜单按钮的回调
const addPermission = (row: Permission) => {
  // 清空数据
  Object.assign(menuData, {
    id: 0,
    code: '',
    level: 0,
    name: '',
    pid: 0,
  })
  // 对话框显示出来
  dialogVisible.value = true
  // 收集新增菜单的level数值
  menuData.level = row.level + 1
  // 给谁新增子菜单
  menuData.pid = row.id as number
}
 
// 编辑已有菜单
const updatePermission = (row: Permission) => {
  // 对话框显示出来
  dialogVisible.value = true
  //点击修改按钮:收集已有的菜单的数据进行更新
  Object.assign(menuData, row)
}
 
// 确认按钮的回调
const save = async () => {
  let result: any = await reqAddOrUpdateMenu(menuData)
  if (result.code === 200) {
    //对话框隐藏
    dialogVisible.value = false
    // 信息提示
    ElMessage({
      type: 'success',
      message: menuData.id ? '更新成功' : '添加成功',
    })
    //再次获取全部最新的菜单的数据
    getHasPermission()
  }
}
 
// 删除按钮的回调
const removeMenu = async (id: number) => {
  let result: any = await reqRemoveMenu(id)
  if (result.code === 200) {
    ElMessage({
      type: 'success',
      message: '删除成功',
    })
    getHasPermission()
  }
}
</script>
 
<style scoped></style>

四、首页

src/views/home/index.vue   

<template>
    <el-card>
        <div class="box">
            <img :src="userStore.avatar" class="avatar">
            <div class="info">
                <h3>{{ getTime() }}好!{{ userStore.username }}</h3>
                <p>椰果甄选运营平台</p>
            </div>
        </div>
    </el-card>
    <div class="bottom">
        <svg-icon name="welcome" width="600px" height="300px"></svg-icon>
    </div>
</template>
 
<script setup lang="ts">
import { getTime } from '@/utils/time'
// 引入用户相关的仓库,获取当前用户的头像、昵称
import useUserStore from '@/store/modules/user'
// 获取存储用户信息的仓库对象
let userStore = useUserStore()
</script>
 
<style scoped lang="scss">
.box {
    display: flex;
    align-items: center;

    .avatar {
        width: 100px;
        height: 100px;
        border-radius: 50%;
    }

    .info {
        margin-left: 20px;

        h3 {
            font-size: 28px;
            font-weight: 500;
        }

        p {
            margin-top: 20px;
            color: #a49a9a;
        }
    }
}

.bottom {
    display: flex;
    justify-content: center;
    margin-top: 100px;
}
</style>

五、暗黑模式和主题颜色的切换

暗黑模式使用可参考:暗黑模式 | Element Plus (element-plus.org) 

主题颜色使用可参考:主题 | Element Plus (element-plus.org) 

暗黑模式需要在main.ts文件中引入所需的样式

import 'element-plus/theme-chalk/dark/css-vars.css'

src/layout/tabbar/setting/index.vue  

<el-popover placement="bottom" title="主题设置" :width="300" trigger="hover">
    <!-- 表单元素 -->
    <el-form>
      <el-form-item label="主题颜色">
        <el-color-picker @click.stop append-to-body @change="setColor" v-model="color" size="small" show-alpha
          :predefine="predefineColors" :teleported="false" />
      </el-form-item>
      <el-form-item label="暗黑模式">
        <el-switch @change="changeDark" v-model="dark" class="mt-2" style="margin-left: 24px" inline-prompt
          active-icon="Moon" inactive-icon="Sunny" />
      </el-form-item>
    </el-form>
    <template #reference>
      <el-button size="small" icon="Setting" circle></el-button>
    </template>
  </el-popover>
 
 
import { ref } from 'vue'
//收集开关的数据
let dark = ref<boolean>(false);
//颜色组件组件的数据
const color = ref('rgba(255, 69, 0, 0.68)')
const predefineColors = ref([
  '#ff4500',
  '#ff8c00',
  '#ffd700',
  '#90ee90',
  '#00ced1',
  '#1e90ff',
  '#c71585',
  'rgba(255, 69, 0, 0.68)',
  'rgb(255, 120, 0)',
  'hsv(51, 100, 98)',
  'hsva(120, 40, 94, 0.5)',
  'hsl(181, 100%, 37%)',
  'hsla(209, 100%, 56%, 0.73)',
  '#c7158577',
])
 
// switch开关的chang事件进行暗黑模式的切换
const changeDark = () => {
  //获取HTML根节点
  let html = document.documentElement
  //判断HTML标签是否有类名dark
  dark.value ? html.className = 'dark' : html.className = ''
}
 
// 主题颜色的设置
const setColor = () => {
  //通知js修改根节点的样式对象的属性与属性值
  let html = document.documentElement
  html.style.setProperty('--el-color-primary', color.value)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/588903.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JAVA面试专题-微服务篇

Spring cloud Spring Cloud 5大组件有哪些 注册中心/配置中心&#xff1a;nacos 负载均衡&#xff1a;Ribbon 服务远程调用&#xff1a;Feign 服务保护&#xff1a;sentinel 服务网关&#xff1a;Gateway 微服务注册和发现 nacos和eureka的区别 负载均衡 微服务向Ribbon发送…

Vue 之 在当前页面的实现分页效果

目录 场景实现 场景 假设&#xff0c;我们现在有这么一个需求&#xff1a; 上述图片的空白内容是活动的&#xff0c;由下面的两个按钮控制上一页、下一页&#xff1b;我们应该可以怎么去实现&#xff1f; 实现 思路&#xff1a; 其实这个问题&#xff0c;我们仿照其他的UI框…

数字旅游以科技创新为动力:推动旅游服务的智能化、网络化和个性化发展,满足游客日益增长的多元化、个性化需求

目录 一、引言 二、科技创新推动旅游服务智能化发展 1、智能化技术的引入与应用 2、智能化提升旅游服务效率与质量 三、科技创新推动旅游服务网络化发展 1、网络化平台的构建与运营 2、网络化拓宽旅游服务渠道与范围 四、科技创新推动旅游服务个性化发展 1、个性化需求…

Sortable 拖拽行实现el-table表格顺序号完整例子,vue 实现表格拖拽行顺序号完整例子

npm install sortable<template><vxe-modalref"modalRef"v-model"showModal"title"详情"width"70vw"height"60vh"class"his"transfer><el-table ref"tableRef" :data"tableData&q…

mysql从入门到起飞+面试基础题

mysql基础 MySQL基础 企业面试题1 代码 select m.id,m.num from ( select t.id as id,count(1) num from ( select ra.requester_id as id from RequestAccepted raunion all select ra.accepter_id as id from RequestAccepted ra ) t group by t.id ) m group by id ord…

鸿蒙原生应用元服务开发-Web加载本地页面

将本地页面文件放在应用的rawfile目录下&#xff0c;开发者可以在Web组件创建的时候指定默认加载的本地页面 &#xff0c;并且加载完成后可通过调用loadUrl()接口变更当前Web组件的页面。 在下面的示例中展示加载本地页面文件的方法&#xff1a; 将资源文件放置在应用的resou…

HarmaonyOS鸿蒙应用科普课

一、什么是鸿蒙OS&#xff1f; 1.概念&#xff1a; 先给大家讲讲今天讲课的主题&#xff0c;鸿蒙OS是什么&#xff1f;鸿蒙系统大家都知道&#xff0c;就是一个操作系统&#xff0c;我们未来是为的成为鸿蒙程序员。所以我们不要将鸿蒙os完全等同于手机操作系统&#xff0c;太…

C#技巧之同步与异步

区别 首先&#xff0c;同步就是程序从上往下顺序执行&#xff0c;要执行完当前流程&#xff0c;才能往下个流程去。 而异步&#xff0c;则是启动当前流程以后&#xff0c;不需要等待流程完成&#xff0c;立刻就去执行下一个流程。 同步示例 创建一个窗体&#xff0c;往窗体里…

Python_GUI框架 Pyside6的信号与槽应用

Python_GUI框架 Pyside6的信号与槽应用 在Pyside6框架中&#xff0c;信号与槽&#xff08;Signals and Slots&#xff09;机制是连接用户界面元素与响应功能的核心机制。我们可以把信号想象成一根电线&#xff0c;而槽就是电线的另一端连接的灯泡。当电线&#xff08;信号&…

贪心算法 Greedy Algorithm

1) 贪心例子 称之为贪心算法或贪婪算法&#xff0c;核心思想是 将寻找最优解的问题分为若干个步骤 每一步骤都采用贪心原则&#xff0c;选取当前最优解 因为没有考虑所有可能&#xff0c;局部最优的堆叠不一定让最终解最优 v2已经不会更新v3因为v3更新过了 贪心算法是一种在…

第八篇:隔离即力量:Python虚拟环境的终极指南

隔离即力量&#xff1a;Python虚拟环境的终极指南 1 引言 在编程的多元宇宙中&#xff0c;Python语言犹如一颗闪耀的星辰&#xff0c;其魅力不仅仅在于简洁的语法&#xff0c;更在于其庞大而繁荣的生态系统。然而&#xff0c;随着应用的增长和复杂性的提升&#xff0c;开发者们…

WinRAR经典压缩神器,高效管理您的文件烈火汉化版 v7.0.

01 软件介绍 WinRAR&#xff0c;作为一款历史悠久且广为人知的压缩文件管理工具&#xff0c;已经成为压缩软件行业的标杆产品。其提供的完整支持覆盖了RAR和ZIP文件格式&#xff0c;同时&#xff0c;该软件还拥有诸多强大的解压缩功能&#xff0c;包括但不限于固体压缩、分卷压…

链表面试题2

1&#xff0c;合并两个有序链表 我们先定义一个虚拟节点newH&#xff0c; 然后按照上图所走&#xff0c;但是当其中一个链表走空时&#xff0c;我们只需返回另一个链表即可 class Solution {public ListNode mergeTwoLists(ListNode headA, ListNode headB) {ListNode newhead…

【C++】滑动窗口:长度最小的子数组

1.题目 2.算法分析 这种题目&#xff0c;首先想到的是暴力穷举法&#xff1a; 用两层循环取遍该数组的所有子数组&#xff0c;然后找到那个最短的就可以了。 我们的滑动窗口就是对这种暴力穷举法进行优化&#xff1a; 主要是舍弃的思想&#xff0c;舍弃那些一定不可能是最终…

03_电子设计教程基础篇(软件推荐)

文章目录 前言一、通用工具软件1.输入法2.截图3.录屏4.桌面管理5.文件检索6.笔记整理7.翻译软件8.AI软件9.文件对比10.思维导图、流程框图、表格软件11.项目托管平台12.解压缩软件13.休闲娱乐软件 二、专业工具软件1.硬件工程师1.原理图、PCB设计2.原理图、PCB仿真3.PCB下单软件…

DNS、ICMP、NAT以及代理服务器

目录 1. DNS 1.1. DNS 背景 1.2. 域名简介 1.3. 域名解析过程 2. ICMP 2.1. ICMP 的功能 2.2. ICMP 的报文格式 2.3. ping 命令 2.4. traceroute 命令 3. NAT和代理服务器 3.1. NAT 技术 3.2. NAT IP转换过程 3.3. NAT 技术的缺陷 3.4. 代理服务器 3.4.1. 正向…

界面组件DevExpress Blazor UI v23.2 - 网格、工具栏功能全新升级

DevExpress Blazor UI组件使用了C#为Blazor Server和Blazor WebAssembly创建高影响力的用户体验&#xff0c;这个UI自建库提供了一套全面的原生Blazor UI组件&#xff08;包括Pivot Grid、调度程序、图表、数据编辑器和报表等&#xff09;。 DevExpress Blazor控件目前已经升级…

通信接口——时钟和信号

前言 所有接口只要抓住三个核心点就能分清&#xff1a;时钟同步和异步&#xff0c;时钟的来源&#xff0c;信号的传输方向。 一、时钟同步和异步 接口之间的交互方式存在多种形式&#xff0c;如果按照是否有公共时钟CLK的参与&#xff0c;可以分为同步传输和异步传输。 同步&…

【Gateway】网关集成Knife4j—swagger接口文档

文章目录 前言一、相关配置1.网关gateway配置①.网关增加配置 pom文件②.网关增加配置 SwaggerHandler③.网关增加配置 SwaggerResourceConfig④.网关增加配置 SwaggerConfig 2.网关过滤器 二、接口文档使用1.访问文档2.查看文档 总结 前言 在日常开发中是需要前后端联调的&am…

Liunx磁盘管理(上)

Liunx磁盘管理&#xff08;中&#xff09;-CSDN博客 目录 一.硬盘类型 机械硬盘&#xff08;HDD&#xff09; 固态硬盘&#xff08;SSD&#xff09; 二.插拔方式 1. 热插拔&#xff08;Hot Swapping&#xff09; 2. 冷插拔&#xff08;Cold Swapping&#xff09; 3. 模块…