bastion_sso/src/pages/RolesPage.vue

181 lines
6.3 KiB
Vue

<template>
<el-card>
<template #header>
<div class='flex justify-between items-center'>
<span class='font-700'>角色管理</span>
<el-button v-if='hasPermission("platform.roles.manage")' type='primary' @click='openCreate'>新增角色</el-button>
</div>
</template>
<el-table :data='rows' v-loading='loading'>
<el-table-column prop='id' label='ID' width='80' sortable />
<el-table-column prop='name' label='角色名' min-width='160' sortable />
<el-table-column label='权限' min-width='420' sortable :sort-method='sortByPermissions'>
<template #default='{ row }'>
<div class='perm-wrap'>
<el-tag v-for='item in row.permissions || []' :key='item.id' size='small' class='perm-tag'>{{ item.name }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column v-if='hasPermission("platform.roles.manage")' label='操作' width='180'>
<template #default='{ row }'>
<div class='btn-gap-8 btn-gap-8--nowrap'>
<el-button size='small' @click='openEdit(row)'>编辑</el-button>
<el-button size='small' type='danger' @click='removeRow(row)'>删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<el-pagination
class='mt-4'
layout='total, sizes, prev, pager, next'
:total='total'
:current-page='page'
:page-size='perPage'
:page-sizes='[10, 20, 50, 100]'
@current-change='handlePageChange'
@size-change='handleSizeChange'
/>
<el-dialog v-model='dialogVisible' :title='editingId ? "编辑角色" : "新增角色"' width='92vw' top='4vh'>
<el-form :model='form' label-width='95px'>
<el-form-item label='角色名'><el-input v-model='form.name' /></el-form-item>
<el-form-item label='权限选择'>
<div class='permission-panel-wrap'>
<el-cascader-panel v-model='selectedPermissionNodes' :options='permissionCascader' :props='cascaderProps' class='permission-panel' />
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click='dialogVisible = false'>取消</el-button>
<el-button type='primary' :loading='saving' @click='submit'>提交</el-button>
</template>
</el-dialog>
</el-card>
</template>
<script setup lang='ts'>
import { ElMessage, ElMessageBox } from 'element-plus'
import { computed, onMounted, reactive, ref } from 'vue'
import { permissionsApi } from '@/api/permissions'
import { rolesApi } from '@/api/roles'
import { buildPermissionCascader, extractPermissionIds } from '@/composables/permission-tree'
import { useAuthStore } from '@/stores/auth'
const authStore = useAuthStore()
const { hasPermission } = authStore
const loading = ref(false)
const saving = ref(false)
const dialogVisible = ref(false)
const editingId = ref<number | null>(null)
const rows = ref<any[]>([])
const total = ref(0)
const page = ref(1)
const perPage = ref(20)
const permissionOptions = ref<any[]>([])
const selectedPermissionNodes = ref<Array<number | string>>([])
const form = reactive<any>({ name: '' })
const cascaderProps = { multiple: true, emitPath: false, checkStrictly: false }
const permissionCascader = computed(() => buildPermissionCascader(permissionOptions.value))
async function fetchList(): Promise<void> {
loading.value = true
try {
const response: any = await rolesApi.list({ page: page.value, per_page: perPage.value })
rows.value = response.data.data || []
total.value = Number(response.data.total || 0)
} finally {
loading.value = false
}
}
function handlePageChange(nextPage: number): void {
page.value = nextPage
fetchList()
}
function handleSizeChange(nextSize: number): void {
perPage.value = nextSize
page.value = 1
fetchList()
}
async function fetchPermissions(): Promise<void> {
const response: any = await permissionsApi.list({ page: 1, per_page: 500 })
permissionOptions.value = response.data.data || []
}
function openCreate(): void {
editingId.value = null
selectedPermissionNodes.value = []
Object.assign(form, { name: '' })
dialogVisible.value = true
}
function openEdit(row: any): void {
editingId.value = row.id
selectedPermissionNodes.value = (row.permissions || []).map((item: any) => item.id)
Object.assign(form, { name: row.name })
dialogVisible.value = true
}
async function submit(): Promise<void> {
saving.value = true
try {
const payload = { name: form.name, permission_ids: extractPermissionIds(selectedPermissionNodes.value) }
if (editingId.value) {
await rolesApi.update(editingId.value, payload)
ElMessage.success('更新成功')
} else {
await rolesApi.create(payload)
ElMessage.success('创建成功')
}
dialogVisible.value = false
await fetchList()
} catch (error: any) {
const first = error?.errors ? (Object.values(error.errors)[0] as string[])?.[0] : null
ElMessage.error(first || error?.message || '提交失败')
} finally {
saving.value = false
}
}
async function removeRow(row: any): Promise<void> {
await ElMessageBox.confirm(`确认删除角色 ${row.name} 吗?`, '提示', { type: 'warning' })
await rolesApi.remove(row.id)
ElMessage.success('删除成功')
await fetchList()
}
onMounted(async () => {
await Promise.all([fetchPermissions(), fetchList()])
})
function sortByPermissions(a: any, b: any): number {
return ((a.permissions || []).map((item: any) => item.name).join(',')).localeCompare((b.permissions || []).map((item: any) => item.name).join(','))
}
</script>
<style scoped>
.perm-wrap { display: flex; flex-wrap: wrap; gap: 6px; }
.perm-tag { margin: 0; }
:deep(.el-dialog) { max-width: 1000px; }
.permission-panel-wrap { width: 100%; overflow: hidden; }
.permission-panel { width: 100%; height: 40vh; min-height: 420px; }
:deep(.permission-panel .el-cascader-menu) { width: 260px; min-width: 260px; }
:deep(.permission-panel .el-cascader-menu__wrap) { height: 100%; overflow-y: auto; overflow-x: hidden; }
:deep(.permission-panel .el-cascader-node__label) {
white-space: nowrap;
overflow: visible;
text-overflow: clip;
line-height: 1.35;
}
:deep(.permission-panel .el-cascader-menu__list) { width: 100%; }
:deep(.permission-panel .el-cascader-node) { width: 100%; }
:deep(.permission-panel .el-cascader-panel) { height: 100%; }
</style>