299 lines
8.7 KiB
Vue
299 lines
8.7 KiB
Vue
<template>
|
|
<div class="page-shell">
|
|
<section class="page-card table-fill">
|
|
<div class="page-toolbar">
|
|
<n-form inline :show-label="false" class="toolbar-form">
|
|
<n-form-item
|
|
><n-input v-model:value="query.keyword" placeholder="角色名/编码" clearable
|
|
/></n-form-item>
|
|
<n-form-item
|
|
><n-select
|
|
v-model:value="query.status"
|
|
:options="statusOptions"
|
|
clearable
|
|
placeholder="状态"
|
|
style="width: 140px"
|
|
/></n-form-item>
|
|
<n-form-item><n-button type="primary" @click="refetch">查询</n-button></n-form-item>
|
|
</n-form>
|
|
<n-button type="primary" @click="openCreate">新增角色</n-button>
|
|
</div>
|
|
<div class="card-body card-body-fill table-fill">
|
|
<n-data-table
|
|
flex-height
|
|
:columns="columns"
|
|
:data="rows"
|
|
:pagination="pagination"
|
|
remote
|
|
@update:page="onPage"
|
|
@update:page-size="onPageSize"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<n-modal
|
|
v-model:show="modal.visible"
|
|
preset="card"
|
|
:title="modal.mode === 'create' ? '新增角色' : '编辑角色'"
|
|
class="w-[560px]"
|
|
>
|
|
<n-form ref="formRef" :model="form" :rules="rules" label-width="90">
|
|
<n-form-item label="角色名称" path="name"
|
|
><n-input v-model:value="form.name"
|
|
/></n-form-item>
|
|
<n-form-item label="角色编码" path="code"
|
|
><n-input v-model:value="form.code" :disabled="modal.mode === 'edit'"
|
|
/></n-form-item>
|
|
<n-form-item label="状态"
|
|
><n-radio-group v-model:value="form.status"
|
|
><n-radio-button value="ENABLED">启用</n-radio-button
|
|
><n-radio-button value="DISABLED">禁用</n-radio-button></n-radio-group
|
|
></n-form-item
|
|
>
|
|
<n-form-item label="数据范围"
|
|
><n-select v-model:value="form.dataScope" :options="dataScopeOptions"
|
|
/></n-form-item>
|
|
<n-form-item label="描述"
|
|
><n-input v-model:value="form.description" type="textarea"
|
|
/></n-form-item>
|
|
</n-form>
|
|
<template #footer
|
|
><div class="flex justify-end gap-2">
|
|
<n-button @click="modal.visible = false">取消</n-button
|
|
><n-button type="primary" @click="save">保存</n-button>
|
|
</div></template
|
|
>
|
|
</n-modal>
|
|
|
|
<n-modal v-model:show="menuModal.visible" preset="card" title="分配菜单" class="w-[620px]">
|
|
<n-tree-select
|
|
v-model:value="menuModal.menuIds"
|
|
multiple
|
|
clearable
|
|
:options="menuTree as any"
|
|
key-field="id"
|
|
label-field="title"
|
|
children-field="children"
|
|
/>
|
|
<template #footer
|
|
><div class="flex justify-end gap-2">
|
|
<n-button @click="menuModal.visible = false">取消</n-button
|
|
><n-button type="primary" @click="saveMenus">保存</n-button>
|
|
</div></template
|
|
>
|
|
</n-modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, h, reactive, ref } from 'vue'
|
|
import {
|
|
NButton,
|
|
NPopconfirm,
|
|
NSpace,
|
|
NTag,
|
|
useMessage,
|
|
type DataTableColumns,
|
|
type FormInst,
|
|
type FormRules
|
|
} from 'naive-ui'
|
|
import { useMutation, useQuery } from '@tanstack/vue-query'
|
|
import {
|
|
createRoleApi,
|
|
deleteRoleApi,
|
|
getRoleDetailApi,
|
|
listRolesApi,
|
|
updateRoleApi,
|
|
updateRoleMenusApi
|
|
} from '@/api/system/role'
|
|
import { listMenusApi } from '@/api/system/menu'
|
|
import type { RoleItem } from '@/types/system/role'
|
|
import { dataScopeLabel, statusLabel, statusTagType } from '@/utils/display'
|
|
import { renderPagePrefix } from '@/utils/pagination'
|
|
|
|
const message = useMessage()
|
|
const query = reactive({ page: 1, pageSize: 10, keyword: '', status: null as string | null })
|
|
const statusOptions = [
|
|
{ label: '启用', value: 'ENABLED' },
|
|
{ label: '禁用', value: 'DISABLED' }
|
|
]
|
|
const dataScopeOptions = [
|
|
{ label: '全部数据', value: 'ALL' },
|
|
{ label: '本企业', value: 'DEPT' },
|
|
{ label: '本企业', value: 'DEPT_ONLY' },
|
|
{ label: '仅本人', value: 'SELF' }
|
|
]
|
|
const roleQuery = useQuery({
|
|
queryKey: computed(() => ['roles', { ...query }]),
|
|
queryFn: () =>
|
|
listRolesApi({
|
|
...query,
|
|
keyword: query.keyword || undefined,
|
|
status: query.status || undefined
|
|
})
|
|
})
|
|
const rows = computed(() => roleQuery.data.value?.items ?? [])
|
|
const pagination = computed(() => ({
|
|
page: query.page,
|
|
pageSize: query.pageSize,
|
|
itemCount: roleQuery.data.value?.total ?? 0,
|
|
showSizePicker: true,
|
|
pageSizes: [10, 20, 50],
|
|
prefix: renderPagePrefix
|
|
}))
|
|
const menuQuery = useQuery({ queryKey: ['menus-all'], queryFn: listMenusApi })
|
|
const menuTree = computed(() => menuQuery.data.value ?? [])
|
|
|
|
const formRef = ref<FormInst | null>(null)
|
|
const modal = reactive({ visible: false, mode: 'create' as 'create' | 'edit', id: '' })
|
|
const form = reactive({ name: '', code: '', status: 'ENABLED', dataScope: 'SELF', description: '' })
|
|
const rules: FormRules = {
|
|
name: [{ required: true, message: '请输入名称' }],
|
|
code: [{ required: true, message: '请输入编码' }]
|
|
}
|
|
const menuModal = reactive({ visible: false, id: '', menuIds: [] as string[] })
|
|
|
|
const saveMutation = useMutation({
|
|
mutationFn: async () => {
|
|
if (modal.mode === 'create') {
|
|
await createRoleApi({
|
|
name: form.name.trim(),
|
|
code: form.code.trim(),
|
|
status: form.status,
|
|
dataScope: form.dataScope.trim() || 'SELF',
|
|
description: form.description.trim() || undefined
|
|
})
|
|
return
|
|
}
|
|
await updateRoleApi(modal.id, {
|
|
name: form.name.trim(),
|
|
status: form.status,
|
|
dataScope: form.dataScope.trim() || 'SELF',
|
|
description: form.description.trim() || undefined
|
|
})
|
|
},
|
|
onSuccess: async () => {
|
|
message.success('保存成功')
|
|
modal.visible = false
|
|
await roleQuery.refetch()
|
|
}
|
|
})
|
|
const deleteMutation = useMutation({
|
|
mutationFn: deleteRoleApi,
|
|
onSuccess: async () => {
|
|
message.success('删除成功')
|
|
await roleQuery.refetch()
|
|
}
|
|
})
|
|
const menuMutation = useMutation({
|
|
mutationFn: () => updateRoleMenusApi(menuModal.id, menuModal.menuIds),
|
|
onSuccess: async () => {
|
|
message.success('分配成功')
|
|
menuModal.visible = false
|
|
}
|
|
})
|
|
|
|
function refetch() {
|
|
query.page = 1
|
|
roleQuery.refetch()
|
|
}
|
|
function onPage(p: number) {
|
|
query.page = p
|
|
}
|
|
function onPageSize(s: number) {
|
|
query.pageSize = s
|
|
query.page = 1
|
|
}
|
|
function openCreate() {
|
|
modal.mode = 'create'
|
|
modal.id = ''
|
|
Object.assign(form, { name: '', code: '', status: 'ENABLED', dataScope: 'SELF', description: '' })
|
|
modal.visible = true
|
|
}
|
|
async function openEdit(row: RoleItem) {
|
|
modal.mode = 'edit'
|
|
modal.id = row.id
|
|
const d = await getRoleDetailApi(row.id)
|
|
Object.assign(form, {
|
|
name: d.name,
|
|
code: d.code,
|
|
status: d.status,
|
|
dataScope: d.dataScope,
|
|
description: d.description ?? ''
|
|
})
|
|
modal.visible = true
|
|
}
|
|
async function openAssign(row: RoleItem) {
|
|
const d = await getRoleDetailApi(row.id)
|
|
menuModal.id = row.id
|
|
menuModal.menuIds = [...d.menuIds]
|
|
menuModal.visible = true
|
|
}
|
|
async function save() {
|
|
await formRef.value?.validate()
|
|
await saveMutation.mutateAsync()
|
|
}
|
|
async function saveMenus() {
|
|
await menuMutation.mutateAsync()
|
|
}
|
|
|
|
const columns = computed<DataTableColumns<RoleItem>>(() => [
|
|
{ title: '名称', key: 'name' },
|
|
{ title: '编码', key: 'code' },
|
|
{
|
|
title: '状态',
|
|
key: 'status',
|
|
render: (r) =>
|
|
h(
|
|
NTag,
|
|
{ size: 'small', type: statusTagType(r.status) },
|
|
{ default: () => statusLabel(r.status) }
|
|
)
|
|
},
|
|
{ title: '数据范围', key: 'dataScope', render: (r) => dataScopeLabel(r.dataScope) },
|
|
{ title: '描述', key: 'description', render: (r) => r.description || '-' },
|
|
{
|
|
title: '操作',
|
|
key: 'actions',
|
|
render: (r) =>
|
|
h(NSpace, { size: 6, align: 'center', class: 'table-action-buttons' }, () => [
|
|
h(
|
|
NButton,
|
|
{
|
|
size: 'small',
|
|
tertiary: true,
|
|
type: 'primary',
|
|
class: 'action-btn',
|
|
onClick: () => openEdit(r)
|
|
},
|
|
{ default: () => '编辑' }
|
|
),
|
|
h(
|
|
NButton,
|
|
{
|
|
size: 'small',
|
|
tertiary: true,
|
|
type: 'info',
|
|
class: 'action-btn',
|
|
onClick: () => openAssign(r)
|
|
},
|
|
{ default: () => '分配菜单' }
|
|
),
|
|
h(
|
|
NPopconfirm,
|
|
{ onPositiveClick: () => deleteMutation.mutate(r.id) },
|
|
{
|
|
trigger: () =>
|
|
h(
|
|
NButton,
|
|
{ size: 'small', tertiary: true, type: 'error', class: 'action-btn' },
|
|
{ default: () => '删除' }
|
|
),
|
|
default: () => '确认删除?'
|
|
}
|
|
)
|
|
])
|
|
}
|
|
])
|
|
</script>
|