Files
Ticket/web/src/features/system/roles/index.vue
T

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>