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

news2024/11/26 2:51:15

一、用户管理

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)
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Golang | Leetcode Golang题解之第64题最小路径和

题目&#xff1a; 题解&#xff1a; func minPathSum(grid [][]int) int {if len(grid) 0 || len(grid[0]) 0 {return 0}rows, columns : len(grid), len(grid[0])dp : make([][]int, rows)for i : 0; i < len(dp); i {dp[i] make([]int, columns)}dp[0][0] grid[0][0]…

vscode 插件 code settings sync(配置云端同步)

vscode 插件 code settings sync&#xff08;配置云端同步&#xff09; 电脑A和B&#xff0c;vscode配置都在A电脑上&#xff0c;此时你想要将A电脑的vscode配置同步到B电脑的vscode中 第一步&#xff1a;A电脑和B电脑都需要在VSCode中安装下图插件 code settings sync 第二步&…

免费开源语音克隆-GPT-SoVITS-WebUI只需 5 秒的声音样本

语音克隆-GPT-SoVITS-WebUI 强大的少样本语音转换与语音合成Web用户界面。 功能&#xff1a; 零样本文本到语音&#xff08;TTS&#xff09;&#xff1a; 输入 5 秒的声音样本&#xff0c;即刻体验文本到语音转换。 少样本 TTS&#xff1a; 仅需 1 分钟的训练数据即可微调模型…

[随记]Mac安装Docker及运行开源Penpot

下载Docker Desktop for Mac&#xff1a;https://www.docker.com/products/docker-desktop/ 安装Docker Desktop for Mac&#xff0c;安装完成后&#xff0c;启动Docker&#xff0c;然后在终端输入&#xff1a; docker version 在Mac电脑的Desktop&#xff0c;随便创建一个文…

国产服务器操作系统部署NTP服务 _ 统信UOS _ 麒麟 _ 中科方德

原文链接&#xff1a;国产服务器操作系统部署NTP服务 | 统信UOS | 麒麟 | 中科方德 Hello&#xff0c;大家好啊&#xff01;在保持服务器时间的精确同步方面&#xff0c;时间同步服务器&#xff08;NTP服务器&#xff09;扮演着至关重要的角色&#xff0c;它能确保系统操作的时…

JAVA面试专题-微服务篇

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

计算机系统的多级层次结构

计算机系统的层次结构 计算机系统最底部的两个底层结构 那我们上一篇文章所举的例子来看:(ps:如果还没有看请查收~各个硬件的工作原理-CSDN博客) 第一条指令是二进制机器指令,它被分为了9个微指令 如下图: 由于传统的机器只能识别二进制指令,而这种指令用来编程是非常不方便的…

与Apollo共创生态:探索自动驾驶的未来蓝图

目录 引言Apollo开放平台Apollo开放平台企业生态计划Apollo X 企业自动驾驶解决方案&#xff1a;加速企业场景应用落地Apollo开放平台携手伙伴共创生态生态共创会员权益 个人心得与展望技术的多元化应用数据驱动的智能化安全与可靠性的重视 结语 引言 就在2024年4月19日&#x…

【抽代复习笔记】17-群(十一):置换的练习题(1)

练习1&#xff1a;计算&#xff1a; 解&#xff1a; 解析&#xff1a;①左边的置换是1保持不变&#xff0c;2变成3&#xff0c;3变成4&#xff0c;4变成5&#xff0c;5变成2&#xff0c;因此可以简写为(2345)&#xff1b;右边的置换是2和5保持不变&#xff0c;1变成3&#xff…

如何将数据导入python

Python导入数据的三种方式&#xff1a; 1、通过标准的Python库导入CSV文件 Python提供了一个标准的类库CSV文件。这个类库中的reader()函数用来导入CSV文件。当CSV文件被读入后&#xff0c;可以利用这些数据生成一个NumPy数组&#xff0c;用来训练算法模型。 from csv import…

IDA pro动态调试so层初级教程

一、开启服务 adb push D:\MyApp\IDA_Pro_7.7\dbgsrv\android_server64 /data/local/tmpadb shell cd /data/local/tmp chmod 777 android_server64 ./android_server64二、IDA附加进程 十万个注意&#xff1a;IDA打开的so文件路径不能有中文 手机打开要调试的app 附加成功

【跟马少平老师学AI】-【神经网络是怎么实现的】(七-1)词向量

一句话归纳&#xff1a; 1&#xff09;神经网络不仅可以处理图像&#xff0c;还可以处理文本。 2&#xff09;神经网络处理文本&#xff0c;先要解决文本的表示&#xff08;图像的表示用像素RGB&#xff09;。 3&#xff09;独热编码词向量&#xff1a; 词表&#xff1a;{我&am…

SQL——高级教程【菜鸟教程】

SQL连接 左连接&#xff1a;SQL LEFT JOIN 关键字 左表相当于主表&#xff0c;不管与右表匹不匹配都会显示所有数据 右表就只会显示和左表匹配的内容。 //例显示&#xff1a;左表的name&#xff0c;有表的总数&#xff0c;时间 SELECT Websites.name, access_log.count, acc…

逻辑漏洞:水平越权、垂直越权靶场练习

目录 1、身份认证失效漏洞实战 2、YXCMS检测数据比对弱&#xff08;水平越权&#xff09; 3、MINICMS权限操作无验证&#xff08;垂直越权&#xff09; 1、身份认证失效漏洞实战 上一篇学习了水平越权和垂直越权的相关基本知识&#xff0c;在本篇还是继续学习&#xff0c;这…

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

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

获取ftp服务器目录树

ChatGPT FTP&#xff08;File Transfer Protocol&#xff09;是一种用于在网络上交换文件的标准互联网协议。当使用FTP客户端连接到FTP服务器时&#xff0c;可以执行多种操作来浏览和管理文件。查看服务器的目录树是常见的操作之一。以下是使用FTP协议查看FTP服务器目录树的一…

【R语言数据分析】函数

目录 自定义函数 apply函数 分类汇总函数aggregate 自定义函数 R语言中的自定义函数更像是在自定义一种运算规则。 自定义函数的语法是 函数名 函数体 } 比如 表示定义了一个名为BMI_function的函数&#xff0c;这个函数代表了一种运算规则&#xff0c;就是把传入的x和…

12_Scala_package

文章目录 Scaal面向对象编程1.回顾Java2.package可以多次声明3.设置作用域&#xff0c;设置上下级4.包可以当作对象使用5.import6.Scala用_取代Java *7.导入多个包8.屏蔽类9.类起别名10.import的规则11.有些包无需导入 Scaal面向对象编程 Scala是一门完全面向对象语言&#xf…

【力扣】203、环形链表 II

142. 环形链表 II 要解决这道题&#xff0c;首先需要对问题进行拆解&#xff1a; 确定链表是否存在环确定环的入口点 如何判断是否存在环呢&#xff1f;这个比较容易想到&#xff0c;使用快慢指针即可判断链表是否存在环。我们定义两个指针&#xff1a; ListNode slow head…

网络安全风险里的威胁建模

文章目录 前言一、威胁建模的必要性二、威胁建模的过程三、威胁建模框架及方法1、NIST威胁模型框架2、STRIDE Model框架3、DREAD框架4、PASTA流程5、LINDDUN框架6、TRIKE知识库7、安全决策树四、威胁建模应用实践前言 网络安全的本质是攻防双方的对抗与博弈。然而,由于多种攻…