日拱一卒无有尽, 功不唐捐终入海
一、接口封装
新建 src / api / role.ts
文件
import request from '@/utils/request'
import { IListParams, Role, RolePostData, Menu, EditRole } from './types/role'
export const getRoles = (params: IListParams) => {
return request<{
count: number
list: Role[]
}>({
method: 'GET',
url: '/setting/role',
params
})
}
export const saveRole = (id: number, data: RolePostData) => {
return request({
method: 'POST',
url: `/setting/role/${id}`,
data
})
}
export const deleteRole = (id: number) => {
return request({
method: 'DELETE',
url: `/setting/role/${id}`
})
}
export const updateRoleStatus = (id: number, status: 0 | 1) => {
return request({
method: 'PUT',
url: `/setting/role/set_status/${id}/${status}`
})
}
export const getRole = (id: number) => {
return request<{
role: EditRole
menus: Menu[]
}>({
method: 'GET',
url: `/setting/role/${id}/edit`
})
}
export const getMenus = () => {
return request<{
menus: Menu[]
}>({
method: 'GET',
url: '/setting/role/create'
})
}
新建 src / api / types / role.ts
文件
export interface Role {
id: number
role_name: string
rules: string
level: number
status: 0 | 1
statusLoading?: boolean
}
export interface IListParams {
page: number
limit: number
status: 0 | 1 | ''
role_name: string
}
export interface RolePostData {
role_name: string
status: 0 | 1
checked_menus: number[]
}
export interface EditRole {
id: number
level: number
role_name: string
rules: string
status: 0 | 1
}
export interface Menu {
pid: number
id: number
title: string
expand: boolean
children?: Menu[]
}
二、ts 类型补充
新建 src / types / element-plus.ts
文件
import { ElForm, ElDialog, ElTree } from 'element-plus'
import type { FormItemRule } from 'element-plus/es/components/form/src/form.type'
export type IElForm = InstanceType<typeof ElForm>
export type IElDialog = InstanceType<typeof ElDialog>
export type IElTree = InstanceType<typeof ElTree>
export type IFormRule = Record<string, FormItemRule[]>
三、角色列表
新建 src / views / setting / permission / role / index.vue
文件
<template>
<el-card>
<template #header>
数据筛选
</template>
<el-form
:inline="true"
ref="form"
:model="listParams"
:disabled="listLoading"
@submit.prevent="handleQuery"
>
<el-form-item label="状态">
<el-select
v-model="listParams.status"
placeholder="请选择"
clearable
>
<el-option
label="全部"
value=""
/>
<el-option
label="显示"
:value="1"
/>
<el-option
label="不显示"
:value="0"
/>
</el-select>
</el-form-item>
<el-form-item label="身份昵称">
<el-input
v-model="listParams.role_name"
clearable
placeholder="请输入身份昵称"
/>
</el-form-item>
<el-form-item>
<el-button native-type="submit">
查询
</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card style="margin-top: 20px;">
<template #header>
<el-button
type="primary"
@click="formVisible = true"
>
添加角色
</el-button>
</template>
<el-table
:data="list"
stripe
style="width: 100%"
v-loading="listLoading"
>
<el-table-column
prop="id"
label="ID"
/>
<el-table-column
prop="role_name"
label="身份昵称"
/>
<el-table-column
prop="rules"
label="权限"
>
<template #default="scope">
<div
class="text-nowrap"
:title="scope.row.rules"
>
{{ scope.row.rules }}
</div>
</template>
</el-table-column>
<el-table-column label="状态">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
:loading="scope.row.statusLoading"
@change="handleStatusChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column
label="操作"
fixed="right"
min-width="100"
align="center"
>
<template #default="scope">
<el-button
type="text"
@click="handleUpdate(scope.row.id)"
>
编辑
</el-button>
<el-popconfirm
title="确认删除吗?"
@confirm="handleDelete(scope.row.id)"
>
<template #reference>
<el-button type="text">
删除
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- <el-pagination
v-model:page="listParams.page"
v-model:limit="listParams.limit"
:list-count="listCount"
:load-list="loadList"
:disabled="listLoading"
/> -->
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:total="listCount"
:page-sizes="[2, 4, 6]"
:disabled="listLoading"
v-model:currentPage="listParams.page"
v-model:pageSize="listParams.limit"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<role-form
v-model="formVisible"
v-model:role-id="roleId"
@success="handleFormSuccess"
/>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { getRoles, deleteRole, updateRoleStatus } from '@/api/role'
import type { IListParams, Role } from '@/api/types/role'
import { ElMessage } from 'element-plus'
import RoleForm from './RoleForm.vue'
const list = ref<Role[]>([]) // 列表数据
const listCount = ref(0)
const listLoading = ref(true)
const listParams = reactive({ // 列表数据查询参数
page: 1, // 当前页码
limit: 10, // 每页大小
role_name: '',
status: '' as IListParams['status']
})
const formVisible = ref(false)
const roleId = ref<number | null>(null)
onMounted(() => {
loadList()
})
const loadList = async () => {
listLoading.value = true
const data = await getRoles(listParams).finally(() => {
listLoading.value = false
})
data.list.forEach(item => {
item.statusLoading = false // 控制切换状态的 loading 效果
})
list.value = data.list
listCount.value = data.count
}
const handleQuery = async () => {
listParams.page = 1 // 查询默认从第1页开始
loadList()
}
const handleDelete = async (id: number) => {
await deleteRole(id)
ElMessage.success('删除成功')
loadList()
}
const handleStatusChange = async (item: Role) => {
item.statusLoading = true
await updateRoleStatus(item.id, item.status).finally(() => {
item.statusLoading = false
})
ElMessage.success(`${item.status === 1 ? '启用' : '禁用'}成功`)
}
const handleUpdate = (id: number) => {
roleId.value = id
formVisible.value = true
}
const handleFormSuccess = () => {
formVisible.value = false
loadList()
}
const handleSizeChange = (size: number) => { // 每页条数
listParams.limit = size
listParams.page = 1
loadList()
}
const handleCurrentChange = (page: number) => { // 当前页数
listParams.page = page
loadList()
}
</script>
<style lang="scss" scoped>
.text-nowrap {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.el-pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
</style>
四、新建 / 编辑 弹窗
新建 src / views / setting / permission / role / RoleForm.vue
文件
<template>
<!-- :confirm="handleSubmit" -->
<el-dialog
ref="dialog"
:title="props.roleId ? '编辑角色' : '添加角色'"
@closed="handleDialogClosed"
@open="handleDialogOpen"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-form
ref="form"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item
label="角色名称"
prop="role_name"
>
<el-input
v-model="formData.role_name"
placeholder="请输入角色名称"
/>
</el-form-item>
<el-form-item label="是否启用">
<el-radio-group v-model="formData.status">
<el-radio
:label="1"
>
开启
</el-radio>
<el-radio
:label="0"
>
关闭
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="角色权限">
<el-tree
ref="tree"
node-key="id"
show-checkbox
:data="menus"
:props="{
label: 'title'
}"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button
type="primary"
@click="handleSubmit"
>确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { nextTick, ref } from 'vue'
import type { PropType } from 'vue'
import type { IElForm, IElTree, IFormRule, IElDialog } from '@/types/element-plus'
import { ElMessage } from 'element-plus'
import { getMenus, getRole, saveRole } from '@/api/role'
import type { Menu } from '@/api/types/role'
const props = defineProps({
roleId: { // 编辑的管理员 ID
type: Number as PropType<number | null>,
default: null
}
})
interface EmitsType {
(e: 'update:role-id', value: number | null): void
(e: 'success'): void
}
const emit = defineEmits<EmitsType>()
const form = ref<IElForm | null>(null)
const formLoading = ref(false)
const formData = ref({
role_name: '',
status: 0 as 0 | 1,
checked_menus: [] as number[]
})
const menus = ref<Menu[]>([]) // 菜单列表
const formRules: IFormRule = {
role_name: [
{ required: true, message: '请输入角色名称', trigger: 'blur' }
]
}
const tree = ref<IElTree | null>(null)
const handleDialogOpen = async () => { // 弹窗打开时触发
formLoading.value = true
props.roleId
? await loadRole().finally(() => { formLoading.value = false })
: await loadMenus().finally(() => { formLoading.value = false })
}
const loadMenus = async () => { // 角色权限菜单
const data = await getMenus()
menus.value = data.menus
}
const loadRole = async () => {
if (!props.roleId) return
const { menus: menusData, role } = await getRole(props.roleId)
menus.value = menusData
await nextTick() // 菜单树渲染完成后处理后面的操作
formData.value.role_name = role.role_name
formData.value.status = role.status
setCheckedMenus(role.rules.split(',').map(id => Number.parseInt(id)))
// formData.value.checked_menus = role.rules.split(',').map(id => Number.parseInt(id))
}
const setCheckedMenus = (menus: number[]) => { // 选中 tree 节点
menus.forEach(menuId => {
const node = tree.value?.getNode(menuId)
if (node && node.isLeaf) { // 判断节点是否是叶子节点
tree.value?.setChecked(menuId, true, false)
}
})
}
const handleDialogClosed = () => { // 弹窗关闭时触发
emit('update:role-id', null)
form.value?.clearValidate() // 清除验证结果
form.value?.resetFields() // 清除表单数据
console.log(props.roleId)
}
const handleSubmit = async () => { // 确认(提交)
const valid = await form.value?.validate()
if (!valid) {
return
}
// 添加 / 更新角色
formData.value.checked_menus = [
...tree.value?.getCheckedKeys(true) as any,
...tree.value?.getHalfCheckedKeys() as any
]
await saveRole(props.roleId || 0, formData.value)
emit('success')
ElMessage.success('保存成功')
}
const dialog = ref<IElDialog | null>(null)
const handleCancel = () => { // 取消
if (dialog.value) {
dialog.value.visible = false
}
}
</script>
<style lang="scss" scoped>
.el-select {
width: 100%;
}
.el-tree {
height: 250px;
overflow: auto;
}
</style>