mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-09-17 19:29:58 +08:00
·fix: 完善用户模块,角色模块
This commit is contained in:
parent
8db80b1fc1
commit
3124d0b923
@ -125,6 +125,7 @@
|
|||||||
"setting": "System settings",
|
"setting": "System settings",
|
||||||
"userCenter": "Personal Center",
|
"userCenter": "Personal Center",
|
||||||
"accountSetting": "User settings",
|
"accountSetting": "User settings",
|
||||||
|
"roleSetting": "Role settings",
|
||||||
"cascader": "Administrative region selection",
|
"cascader": "Administrative region selection",
|
||||||
"dict": "Dictionary example"
|
"dict": "Dictionary example"
|
||||||
},
|
},
|
||||||
|
@ -150,6 +150,7 @@
|
|||||||
"justSuper": "super可见",
|
"justSuper": "super可见",
|
||||||
"setting": "系统设置",
|
"setting": "系统设置",
|
||||||
"accountSetting": "用户设置",
|
"accountSetting": "用户设置",
|
||||||
|
"roleSetting": "角色设置",
|
||||||
"dictionarySetting": "字典设置",
|
"dictionarySetting": "字典设置",
|
||||||
"menuSetting": "菜单设置",
|
"menuSetting": "菜单设置",
|
||||||
"userCenter": "个人中心",
|
"userCenter": "个人中心",
|
||||||
|
@ -44,6 +44,6 @@ export function deleteMenu(id: number) {
|
|||||||
* 查询菜单树
|
* 查询菜单树
|
||||||
* GET /menu/selectTree
|
* GET /menu/selectTree
|
||||||
*/
|
*/
|
||||||
export function selectMenuTree() {
|
export function getMenuOptions() {
|
||||||
return request.Get<Api.Response<Entity.TreeNode[]>>('/menu/selectTree')
|
return request.Get<Api.Response<Entity.TreeNode[]>>('/menu/options')
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,14 @@ export function getRoleList(params?: RoleQueryParams) {
|
|||||||
return request.Get<Api.ListResponse<Entity.Role>>('/role', { params })
|
return request.Get<Api.ListResponse<Entity.Role>>('/role', { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询角色选项
|
||||||
|
* GET /role/options
|
||||||
|
*/
|
||||||
|
export function getRoleOptions() {
|
||||||
|
return request.Get<Api.Response<Entity.TreeNode[]>>('/role/options')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询角色详情
|
* 查询角色详情
|
||||||
* GET /role/{id}
|
* GET /role/{id}
|
||||||
|
@ -38,7 +38,6 @@ export function setupRouterGuard(router: Router) {
|
|||||||
// 如果是login路由,直接放行
|
// 如果是login路由,直接放行
|
||||||
if (to.name === 'login') {
|
if (to.name === 'login') {
|
||||||
// login页面不需要任何认证检查,直接放行
|
// login页面不需要任何认证检查,直接放行
|
||||||
// 继续执行后面的逻辑
|
|
||||||
}
|
}
|
||||||
// 如果路由明确设置了requiresAuth为false,直接放行
|
// 如果路由明确设置了requiresAuth为false,直接放行
|
||||||
else if (to.meta.requiresAuth === false) {
|
else if (to.meta.requiresAuth === false) {
|
||||||
@ -70,6 +69,7 @@ export function setupRouterGuard(router: Router) {
|
|||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
// 如果路由初始化失败(比如 401 错误),重定向到登录页
|
// 如果路由初始化失败(比如 401 错误),重定向到登录页
|
||||||
|
local.remove('accessToken')
|
||||||
const redirect = to.fullPath !== '/' ? to.fullPath : undefined
|
const redirect = to.fullPath !== '/' ? to.fullPath : undefined
|
||||||
next({ path: '/login', query: redirect ? { redirect } : undefined })
|
next({ path: '/login', query: redirect ? { redirect } : undefined })
|
||||||
return
|
return
|
||||||
|
@ -350,6 +350,16 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
id: 701,
|
id: 701,
|
||||||
parentId: 7,
|
parentId: 7,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'roleSetting',
|
||||||
|
path: '/setting/role',
|
||||||
|
title: '角色设置',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:every-user',
|
||||||
|
component: '/setting/role/index.vue',
|
||||||
|
id: 702,
|
||||||
|
parentId: 7,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'dictionarySetting',
|
name: 'dictionarySetting',
|
||||||
path: '/setting/dict',
|
path: '/setting/dict',
|
||||||
@ -357,7 +367,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:book-one',
|
icon: 'icon-park-outline:book-one',
|
||||||
component: '/setting/dict/index.vue',
|
component: '/setting/dict/index.vue',
|
||||||
id: 702,
|
id: 703,
|
||||||
parentId: 7,
|
parentId: 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -367,7 +377,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:application-menu',
|
icon: 'icon-park-outline:application-menu',
|
||||||
component: '/setting/menu/index.vue',
|
component: '/setting/menu/index.vue',
|
||||||
id: 703,
|
id: 704,
|
||||||
parentId: 7,
|
parentId: 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
12
src/typings/entities/role.d.ts
vendored
12
src/typings/entities/role.d.ts
vendored
@ -5,6 +5,10 @@ namespace Entity {
|
|||||||
type RoleType = string
|
type RoleType = string
|
||||||
|
|
||||||
interface Role {
|
interface Role {
|
||||||
|
/**
|
||||||
|
* 角色ID
|
||||||
|
*/
|
||||||
|
roleId: number
|
||||||
/**
|
/**
|
||||||
* 菜单ID数组
|
* 菜单ID数组
|
||||||
*/
|
*/
|
||||||
@ -29,6 +33,14 @@ namespace Entity {
|
|||||||
* 显示顺序
|
* 显示顺序
|
||||||
*/
|
*/
|
||||||
sort: number
|
sort: number
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
createTime?: string
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
updateTime?: string
|
||||||
[property: string]: any
|
[property: string]: any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<
|
|||||||
{
|
{
|
||||||
title: '菜单名称',
|
title: '菜单名称',
|
||||||
key: 'title',
|
key: 'title',
|
||||||
width: 200,
|
width: 400,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '图标',
|
title: '图标',
|
||||||
@ -55,7 +55,7 @@ export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<
|
|||||||
const menuTypeMap = {
|
const menuTypeMap = {
|
||||||
directory: { label: '目录', type: 'primary' },
|
directory: { label: '目录', type: 'primary' },
|
||||||
page: { label: '菜单', type: 'warning' },
|
page: { label: '菜单', type: 'warning' },
|
||||||
permission: { label: '按钮', type: 'success' },
|
permission: { label: '权限', type: 'info' },
|
||||||
} as const
|
} as const
|
||||||
const menuInfo = menuTypeMap[row.menuType]
|
const menuInfo = menuTypeMap[row.menuType]
|
||||||
return <NTag type={menuInfo.type} bordered={false}>{menuInfo.label}</NTag>
|
return <NTag type={menuInfo.type} bordered={false}>{menuInfo.label}</NTag>
|
||||||
@ -82,13 +82,15 @@ export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<
|
|||||||
render: (row) => {
|
render: (row) => {
|
||||||
return (
|
return (
|
||||||
<NSpace justify="center">
|
<NSpace justify="center">
|
||||||
<NButton
|
{row.menuType !== 'permission' && (
|
||||||
text
|
<NButton
|
||||||
type="primary"
|
text
|
||||||
onClick={() => onAdd(row)}
|
type="primary"
|
||||||
>
|
onClick={() => onAdd(row)}
|
||||||
新建
|
>
|
||||||
</NButton>
|
新建
|
||||||
|
</NButton>
|
||||||
|
)}
|
||||||
<NButton
|
<NButton
|
||||||
text
|
text
|
||||||
onClick={() => onEdit(row)}
|
onClick={() => onEdit(row)}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useBoolean } from '@/hooks'
|
import { useBoolean } from '@/hooks'
|
||||||
import { createMenu, getMenuById, selectMenuTree, updateMenu } from '@/api'
|
import { createMenu, getMenuById, getMenuOptions, updateMenu } from '@/api'
|
||||||
import { createProModalForm } from 'pro-naive-ui'
|
import { createProModalForm } from 'pro-naive-ui'
|
||||||
import DirectoryForm from './DirectoryForm.vue'
|
import DirectoryForm from './DirectoryForm.vue'
|
||||||
import PageForm from './PageForm.vue'
|
import PageForm from './PageForm.vue'
|
||||||
@ -63,7 +63,7 @@ const currentFormComponent = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function openModal(type: ModalType = 'add', data?: Partial<Entity.Menu>) {
|
async function openModal(type: ModalType = 'add', data?: Partial<Entity.Menu>) {
|
||||||
selectMenuTree().then((res) => {
|
getMenuOptions().then((res) => {
|
||||||
treeData.value = res.data
|
treeData.value = res.data
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -74,6 +74,7 @@ async function openModal(type: ModalType = 'add', data?: Partial<Entity.Menu>) {
|
|||||||
// 如果新建传入了menuId,设置为父级菜单
|
// 如果新建传入了menuId,设置为父级菜单
|
||||||
if (data?.id) {
|
if (data?.id) {
|
||||||
modalForm.values.value.parentId = data.id
|
modalForm.values.value.parentId = data.id
|
||||||
|
modalForm.values.value.path = `${data.path}/`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async edit() {
|
async edit() {
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
path="path"
|
path="path"
|
||||||
class="col-span-2"
|
class="col-span-2"
|
||||||
placeholder="Eg: /system/user"
|
placeholder="Eg: /system/user"
|
||||||
|
@update:value="$emit('path', $event)"
|
||||||
/>
|
/>
|
||||||
<pro-input
|
<pro-input
|
||||||
title="高亮菜单路径"
|
title="高亮菜单路径"
|
||||||
|
@ -71,7 +71,7 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<pro-data-table
|
<pro-data-table
|
||||||
row-key="menuId"
|
row-key="id"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
132
src/views/setting/role/columns.tsx
Normal file
132
src/views/setting/role/columns.tsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
import { NButton, NPopconfirm, NSpace, NSwitch } from 'naive-ui'
|
||||||
|
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
||||||
|
import { renderProCopyableText, renderProDateText } from 'pro-naive-ui'
|
||||||
|
|
||||||
|
export const searchColumns: ProSearchFormColumns<Entity.Role> = [
|
||||||
|
{
|
||||||
|
title: '角色名称',
|
||||||
|
path: 'roleName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '角色权限',
|
||||||
|
path: 'roleKey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
path: 'roleStatus',
|
||||||
|
field: 'select',
|
||||||
|
fieldProps: {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '启用',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '禁用',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 角色管理columns配置函数
|
||||||
|
interface RoleColumnActions {
|
||||||
|
onEdit: (row: Entity.Role) => void
|
||||||
|
onDelete: (id: number) => void
|
||||||
|
onStatusChange: (value: 0 | 1, id: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRoleColumns(actions: RoleColumnActions): DataTableColumns<Entity.Role> {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: '角色ID',
|
||||||
|
align: 'center',
|
||||||
|
key: 'roleId',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '角色名称',
|
||||||
|
align: 'center',
|
||||||
|
key: 'roleName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '角色权限',
|
||||||
|
align: 'center',
|
||||||
|
key: 'roleKey',
|
||||||
|
render: row => renderProCopyableText(row.roleKey),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '备注',
|
||||||
|
align: 'center',
|
||||||
|
key: 'remark',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '排序',
|
||||||
|
align: 'center',
|
||||||
|
key: 'sort',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
align: 'center',
|
||||||
|
key: 'roleStatus',
|
||||||
|
width: 100,
|
||||||
|
render: (row) => {
|
||||||
|
return (
|
||||||
|
<NSwitch
|
||||||
|
value={row.roleStatus || 1}
|
||||||
|
checked-value={1}
|
||||||
|
unchecked-value={0}
|
||||||
|
onUpdateValue={(value: 0 | 1) =>
|
||||||
|
actions.onStatusChange(value, row.roleId)}
|
||||||
|
>
|
||||||
|
{{ checked: () => '启用', unchecked: () => '禁用' }}
|
||||||
|
</NSwitch>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
align: 'center',
|
||||||
|
key: 'createTime',
|
||||||
|
width: 200,
|
||||||
|
render: row => renderProDateText(row.createTime, {
|
||||||
|
pattern: 'datetime',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
align: 'center',
|
||||||
|
key: 'actions',
|
||||||
|
width: 200,
|
||||||
|
render: (row) => {
|
||||||
|
return (
|
||||||
|
<NSpace justify="center">
|
||||||
|
<NButton
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
onClick={() => actions.onEdit(row)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</NButton>
|
||||||
|
<NPopconfirm
|
||||||
|
onPositiveClick={() => actions.onDelete(row.roleId)}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
default: () => '确认删除该角色?',
|
||||||
|
trigger: () => (
|
||||||
|
<NButton size="small" type="error">
|
||||||
|
删除
|
||||||
|
</NButton>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
</NPopconfirm>
|
||||||
|
</NSpace>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
134
src/views/setting/role/components/RoleModal.vue
Normal file
134
src/views/setting/role/components/RoleModal.vue
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useBoolean } from '@/hooks'
|
||||||
|
import { createRole, getRoleById, updateRole } from '@/api'
|
||||||
|
import { createProModalForm } from 'pro-naive-ui'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modalName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
modalName = '',
|
||||||
|
} = defineProps<Props>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
success: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||||
|
|
||||||
|
const modalForm = createProModalForm<Partial<Entity.Role>>({
|
||||||
|
omitEmptyString: false,
|
||||||
|
initialValues: {
|
||||||
|
roleName: '',
|
||||||
|
roleKey: '',
|
||||||
|
remark: '',
|
||||||
|
sort: 0,
|
||||||
|
roleStatus: 1,
|
||||||
|
},
|
||||||
|
onSubmit: submitModal,
|
||||||
|
})
|
||||||
|
|
||||||
|
type ModalType = 'add' | 'edit'
|
||||||
|
const modalType = shallowRef<ModalType>('add')
|
||||||
|
const modalTitle = computed(() => {
|
||||||
|
const titleMap: Record<ModalType, string> = {
|
||||||
|
add: '添加',
|
||||||
|
edit: '编辑',
|
||||||
|
}
|
||||||
|
return `${titleMap[modalType.value]}${modalName}`
|
||||||
|
})
|
||||||
|
|
||||||
|
async function openModal(type: ModalType = 'add', data?: Partial<Entity.Role>) {
|
||||||
|
modalType.value = type
|
||||||
|
|
||||||
|
modalForm.open()
|
||||||
|
const handlers = {
|
||||||
|
async add() {
|
||||||
|
// 使用默认值
|
||||||
|
},
|
||||||
|
async edit() {
|
||||||
|
if (!data)
|
||||||
|
return
|
||||||
|
|
||||||
|
const { data: role } = await getRoleById(data.roleId!)
|
||||||
|
modalForm.values.value = role
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await handlers[type]()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitModal(filedValues: Partial<Entity.Role>) {
|
||||||
|
const handlers = {
|
||||||
|
async add() {
|
||||||
|
try {
|
||||||
|
await createRole(filedValues)
|
||||||
|
window.$message.success('角色创建成功')
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('创建角色失败', error)
|
||||||
|
window.$message.error('创建角色失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async edit() {
|
||||||
|
try {
|
||||||
|
await updateRole(modalForm.values.value.roleId!, filedValues)
|
||||||
|
window.$message.success('角色更新成功')
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('更新角色失败', error)
|
||||||
|
window.$message.error('更新角色失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
startLoading()
|
||||||
|
await handlers[modalType.value]()
|
||||||
|
endLoading()
|
||||||
|
emit('success')
|
||||||
|
modalForm.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openModal,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<pro-modal-form
|
||||||
|
:title="modalTitle"
|
||||||
|
:form="modalForm"
|
||||||
|
:loading="submitLoading"
|
||||||
|
width="600px"
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<pro-input
|
||||||
|
required
|
||||||
|
title="角色名称"
|
||||||
|
path="roleName"
|
||||||
|
placeholder="请输入角色名称"
|
||||||
|
/>
|
||||||
|
<pro-input
|
||||||
|
required
|
||||||
|
title="角色权限"
|
||||||
|
path="roleKey"
|
||||||
|
placeholder="请输入角色权限"
|
||||||
|
/>
|
||||||
|
<pro-textarea
|
||||||
|
class="col-span-2"
|
||||||
|
title="备注"
|
||||||
|
path="remark"
|
||||||
|
placeholder="请输入备注信息"
|
||||||
|
/>
|
||||||
|
<pro-digit
|
||||||
|
title="排序"
|
||||||
|
path="sort"
|
||||||
|
:field-props="{ min: 0, max: 999 }"
|
||||||
|
/>
|
||||||
|
<pro-switch
|
||||||
|
title="状态"
|
||||||
|
path="roleStatus"
|
||||||
|
:field-props="{ checkedValue: 1, uncheckedValue: 0 }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</pro-modal-form>
|
||||||
|
</template>
|
90
src/views/setting/role/index.vue
Normal file
90
src/views/setting/role/index.vue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<script setup lang="tsx">
|
||||||
|
import { createProSearchForm, useNDataTable } from 'pro-naive-ui'
|
||||||
|
import { deleteRole, getRoleList } from '@/api'
|
||||||
|
import { createRoleColumns, searchColumns } from './columns'
|
||||||
|
import RoleModal from './components/RoleModal.vue'
|
||||||
|
|
||||||
|
const searchForm = createProSearchForm({
|
||||||
|
initialValues: {
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
table: {
|
||||||
|
tableProps,
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
proSearchFormProps,
|
||||||
|
},
|
||||||
|
refresh,
|
||||||
|
} = useNDataTable(getRolePage, {
|
||||||
|
form: searchForm,
|
||||||
|
})
|
||||||
|
|
||||||
|
const modalRef = ref()
|
||||||
|
|
||||||
|
async function deleteRoleData(id: number) {
|
||||||
|
try {
|
||||||
|
await deleteRole(id)
|
||||||
|
window.$message.success('角色删除成功')
|
||||||
|
refresh() // 重新加载列表
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
window.$message.error('角色删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tablecolumns = createRoleColumns({
|
||||||
|
onEdit: (row: Entity.Role) => modalRef.value?.openModal('edit', row),
|
||||||
|
onDelete: deleteRoleData,
|
||||||
|
onStatusChange: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
async function getRolePage({ current, pageSize }: any, formData: Entity.Role[]) {
|
||||||
|
try {
|
||||||
|
const { data } = await getRoleList({
|
||||||
|
...formData,
|
||||||
|
pageNum: current,
|
||||||
|
pageSize,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
list: data.list,
|
||||||
|
total: data.total,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return {
|
||||||
|
list: [],
|
||||||
|
total: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-space vertical>
|
||||||
|
<n-card>
|
||||||
|
<pro-search-form
|
||||||
|
:form="searchForm"
|
||||||
|
:columns="searchColumns"
|
||||||
|
v-bind="proSearchFormProps"
|
||||||
|
:collapse-button-props="false"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<pro-data-table
|
||||||
|
:columns="tablecolumns"
|
||||||
|
v-bind="tableProps"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<n-button type="primary" @click="modalRef.openModal('add')">
|
||||||
|
<template #icon>
|
||||||
|
<icon-park-outline-plus />
|
||||||
|
</template>
|
||||||
|
新建角色
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</pro-data-table>
|
||||||
|
<RoleModal ref="modalRef" modal-name="角色" @success="refresh" />
|
||||||
|
</n-space>
|
||||||
|
</template>
|
@ -72,6 +72,7 @@ export function createUserColumns(actions: UserColumnActions): DataTableColumns<
|
|||||||
title: '邮箱',
|
title: '邮箱',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
key: 'email',
|
key: 'email',
|
||||||
|
render: row => renderProCopyableText(row.email),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '手机号',
|
title: '手机号',
|
||||||
@ -83,6 +84,7 @@ export function createUserColumns(actions: UserColumnActions): DataTableColumns<
|
|||||||
title: '状态',
|
title: '状态',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
key: 'userStatus',
|
key: 'userStatus',
|
||||||
|
width: 100,
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
return (
|
return (
|
||||||
<NSwitch
|
<NSwitch
|
||||||
@ -101,6 +103,7 @@ export function createUserColumns(actions: UserColumnActions): DataTableColumns<
|
|||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
key: 'createTime',
|
key: 'createTime',
|
||||||
|
width: 200,
|
||||||
render: row => renderProDateText(row.createTime, {
|
render: row => renderProDateText(row.createTime, {
|
||||||
pattern: 'datetime',
|
pattern: 'datetime',
|
||||||
}),
|
}),
|
||||||
@ -109,6 +112,7 @@ export function createUserColumns(actions: UserColumnActions): DataTableColumns<
|
|||||||
title: '操作',
|
title: '操作',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
|
width: 120,
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
return (
|
return (
|
||||||
<NSpace justify="center">
|
<NSpace justify="center">
|
||||||
|
@ -1,238 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useBoolean } from '@/hooks'
|
|
||||||
import { createUser, fetchRoleList, updateUser } from '@/api'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
modalName?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
modalName = '',
|
|
||||||
} = defineProps<Props>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
open: []
|
|
||||||
close: []
|
|
||||||
success: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const { bool: modalVisible, setTrue: showModal, setFalse: hiddenModal } = useBoolean(false)
|
|
||||||
|
|
||||||
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
|
||||||
|
|
||||||
const formDefault = {
|
|
||||||
userId: 0,
|
|
||||||
username: '',
|
|
||||||
nickName: '',
|
|
||||||
email: '',
|
|
||||||
phone: '',
|
|
||||||
gender: 'unknown' as 'male' | 'female' | 'unknown',
|
|
||||||
deptId: undefined,
|
|
||||||
userStatus: 1,
|
|
||||||
roles: [] as any[],
|
|
||||||
remark: '',
|
|
||||||
}
|
|
||||||
const formModel = ref<Partial<Entity.User>>({ ...formDefault })
|
|
||||||
|
|
||||||
type ModalType = 'add' | 'view' | 'edit'
|
|
||||||
const modalType = shallowRef<ModalType>('add')
|
|
||||||
const modalTitle = computed(() => {
|
|
||||||
const titleMap: Record<ModalType, string> = {
|
|
||||||
add: '添加',
|
|
||||||
view: '查看',
|
|
||||||
edit: '编辑',
|
|
||||||
}
|
|
||||||
return `${titleMap[modalType.value]}${modalName}`
|
|
||||||
})
|
|
||||||
|
|
||||||
async function openModal(type: ModalType = 'add', data: any) {
|
|
||||||
emit('open')
|
|
||||||
modalType.value = type
|
|
||||||
showModal()
|
|
||||||
getRoleList()
|
|
||||||
const handlers = {
|
|
||||||
async add() {
|
|
||||||
formModel.value = { ...formDefault }
|
|
||||||
},
|
|
||||||
async view() {
|
|
||||||
if (!data)
|
|
||||||
return
|
|
||||||
formModel.value = { ...data }
|
|
||||||
},
|
|
||||||
async edit() {
|
|
||||||
if (!data)
|
|
||||||
return
|
|
||||||
formModel.value = { ...data }
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await handlers[type]()
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
hiddenModal()
|
|
||||||
endLoading()
|
|
||||||
emit('close')
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
openModal,
|
|
||||||
})
|
|
||||||
|
|
||||||
const formRef = ref()
|
|
||||||
async function submitModal() {
|
|
||||||
const handlers = {
|
|
||||||
async add() {
|
|
||||||
try {
|
|
||||||
await createUser({
|
|
||||||
username: formModel.value.username!,
|
|
||||||
password: '123456', // 默认密码
|
|
||||||
nickName: formModel.value.nickName,
|
|
||||||
email: formModel.value.email,
|
|
||||||
phone: formModel.value.phone,
|
|
||||||
gender: formModel.value.gender,
|
|
||||||
deptId: formModel.value.deptId,
|
|
||||||
remark: formModel.value.remark,
|
|
||||||
})
|
|
||||||
window.$message.success('用户创建成功')
|
|
||||||
emit('success')
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
window.$message.error('创建用户失败')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async edit() {
|
|
||||||
try {
|
|
||||||
await updateUser(formModel.value.userId!, {
|
|
||||||
nickName: formModel.value.nickName,
|
|
||||||
email: formModel.value.email,
|
|
||||||
phone: formModel.value.phone,
|
|
||||||
gender: formModel.value.gender,
|
|
||||||
userStatus: formModel.value.userStatus,
|
|
||||||
deptId: formModel.value.deptId,
|
|
||||||
remark: formModel.value.remark,
|
|
||||||
})
|
|
||||||
window.$message.success('用户更新成功')
|
|
||||||
emit('success')
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
window.$message.error('更新用户失败')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async view() {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await formRef.value?.validate()
|
|
||||||
startLoading()
|
|
||||||
const success = await handlers[modalType.value]()
|
|
||||||
endLoading()
|
|
||||||
if (success) {
|
|
||||||
closeModal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rules = {
|
|
||||||
username: {
|
|
||||||
required: true,
|
|
||||||
message: '请输入用户名',
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
required: false,
|
|
||||||
pattern: /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/,
|
|
||||||
message: '请输入正确的邮箱格式',
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
phone: {
|
|
||||||
required: false,
|
|
||||||
pattern: /^1[3-9]\d{9}$/,
|
|
||||||
message: '请输入正确的手机号格式',
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = ref()
|
|
||||||
async function getRoleList() {
|
|
||||||
const { data } = await fetchRoleList()
|
|
||||||
options.value = data
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-modal
|
|
||||||
v-model:show="modalVisible"
|
|
||||||
:mask-closable="false"
|
|
||||||
preset="card"
|
|
||||||
:title="modalTitle"
|
|
||||||
class="w-700px"
|
|
||||||
:segmented="{
|
|
||||||
content: true,
|
|
||||||
action: true,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<n-form ref="formRef" :rules="rules" label-placement="left" :model="formModel" :label-width="100" :disabled="modalType === 'view'">
|
|
||||||
<n-grid :cols="2" :x-gap="18">
|
|
||||||
<n-form-item-grid-item :span="1" label="用户名" path="username">
|
|
||||||
<n-input v-model:value="formModel.username" :disabled="modalType === 'edit'" />
|
|
||||||
</n-form-item-grid-item>
|
|
||||||
<n-form-item-grid-item :span="1" label="昵称" path="nickName">
|
|
||||||
<n-input v-model:value="formModel.nickName" />
|
|
||||||
</n-form-item-grid-item>
|
|
||||||
<n-form-item-grid-item :span="1" label="性别" path="gender">
|
|
||||||
<n-radio-group v-model:value="formModel.gender">
|
|
||||||
<n-space>
|
|
||||||
<n-radio value="male">
|
|
||||||
男
|
|
||||||
</n-radio>
|
|
||||||
<n-radio value="female">
|
|
||||||
女
|
|
||||||
</n-radio>
|
|
||||||
<n-radio value="unknown">
|
|
||||||
未知
|
|
||||||
</n-radio>
|
|
||||||
</n-space>
|
|
||||||
</n-radio-group>
|
|
||||||
</n-form-item-grid-item>
|
|
||||||
<n-form-item-grid-item :span="1" label="部门ID" path="deptId">
|
|
||||||
<n-input-number v-model:value="formModel.deptId" class="w-full" />
|
|
||||||
</n-form-item-grid-item>
|
|
||||||
<n-form-item-grid-item :span="1" label="邮箱" path="email">
|
|
||||||
<n-input v-model:value="formModel.email" />
|
|
||||||
</n-form-item-grid-item>
|
|
||||||
<n-form-item-grid-item :span="1" label="手机号" path="phone">
|
|
||||||
<n-input v-model:value="formModel.phone" />
|
|
||||||
</n-form-item-grid-item>
|
|
||||||
<n-form-item-grid-item :span="2" label="备注" path="remark">
|
|
||||||
<n-input v-model:value="formModel.remark" type="textarea" />
|
|
||||||
</n-form-item-grid-item>
|
|
||||||
<n-form-item-grid-item :span="1" label="用户状态" path="userStatus">
|
|
||||||
<n-switch
|
|
||||||
v-model:value="formModel.userStatus"
|
|
||||||
:checked-value="1" :unchecked-value="0"
|
|
||||||
>
|
|
||||||
<template #checked>
|
|
||||||
启用
|
|
||||||
</template>
|
|
||||||
<template #unchecked>
|
|
||||||
禁用
|
|
||||||
</template>
|
|
||||||
</n-switch>
|
|
||||||
</n-form-item-grid-item>
|
|
||||||
</n-grid>
|
|
||||||
</n-form>
|
|
||||||
<template #action>
|
|
||||||
<n-space justify="center">
|
|
||||||
<n-button @click="closeModal">
|
|
||||||
取消
|
|
||||||
</n-button>
|
|
||||||
<n-button type="primary" :loading="submitLoading" @click="submitModal">
|
|
||||||
提交
|
|
||||||
</n-button>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
|
||||||
</template>
|
|
154
src/views/setting/user/components/UserModal.vue
Normal file
154
src/views/setting/user/components/UserModal.vue
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useBoolean } from '@/hooks'
|
||||||
|
import { createUser, getRoleOptions, getUserById, updateUser } from '@/api'
|
||||||
|
import { createProModalForm } from 'pro-naive-ui'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modalName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
modalName = '',
|
||||||
|
} = defineProps<Props>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
success: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||||
|
|
||||||
|
const modalForm = createProModalForm<Partial<Entity.User>>({
|
||||||
|
omitEmptyString: false,
|
||||||
|
initialValues: {
|
||||||
|
gender: 'unknown',
|
||||||
|
userStatus: 1,
|
||||||
|
roles: [],
|
||||||
|
},
|
||||||
|
onSubmit: submitModal,
|
||||||
|
})
|
||||||
|
|
||||||
|
type ModalType = 'add' | 'edit'
|
||||||
|
const modalType = shallowRef<ModalType>('add')
|
||||||
|
const modalTitle = computed(() => {
|
||||||
|
const titleMap: Record<ModalType, string> = {
|
||||||
|
add: '添加',
|
||||||
|
edit: '编辑',
|
||||||
|
}
|
||||||
|
return `${titleMap[modalType.value]}${modalName}`
|
||||||
|
})
|
||||||
|
const roleOptions = ref<Entity.TreeNode[]>([])
|
||||||
|
|
||||||
|
async function openModal(type: ModalType = 'add', data?: Partial<Entity.User>) {
|
||||||
|
modalType.value = type
|
||||||
|
getRoleOptions().then((res) => {
|
||||||
|
roleOptions.value = res.data
|
||||||
|
})
|
||||||
|
modalForm.open()
|
||||||
|
const handlers = {
|
||||||
|
async add() {
|
||||||
|
},
|
||||||
|
async edit() {
|
||||||
|
if (!data)
|
||||||
|
return
|
||||||
|
|
||||||
|
const { data: user } = await getUserById(data.userId!)
|
||||||
|
modalForm.values.value = user
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await handlers[type]()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitModal(filedValues: Partial<Entity.User>) {
|
||||||
|
const handlers = {
|
||||||
|
async add() {
|
||||||
|
try {
|
||||||
|
await createUser(filedValues)
|
||||||
|
window.$message.success('用户创建成功')
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('创建用户失败', error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async edit() {
|
||||||
|
try {
|
||||||
|
await updateUser(modalForm.values.value.userId!, filedValues)
|
||||||
|
window.$message.success('用户更新成功')
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('更新用户失败', error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
startLoading()
|
||||||
|
await handlers[modalType.value]()
|
||||||
|
endLoading()
|
||||||
|
emit('success')
|
||||||
|
modalForm.close()
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
openModal,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<pro-modal-form
|
||||||
|
:title="modalTitle"
|
||||||
|
:form="modalForm"
|
||||||
|
:loading="submitLoading"
|
||||||
|
width="700px"
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-2">
|
||||||
|
<pro-input
|
||||||
|
required
|
||||||
|
title="用户名"
|
||||||
|
path="username"
|
||||||
|
:readonly="modalType === 'edit'"
|
||||||
|
/>
|
||||||
|
<pro-input
|
||||||
|
title="昵称"
|
||||||
|
path="nickName"
|
||||||
|
/>
|
||||||
|
<pro-radio-group
|
||||||
|
title="性别"
|
||||||
|
path="gender"
|
||||||
|
:field-props="{
|
||||||
|
type: 'button',
|
||||||
|
options: [
|
||||||
|
{ label: '男', value: 'male' },
|
||||||
|
{ label: '女', value: 'female' },
|
||||||
|
],
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<pro-digit
|
||||||
|
title="部门ID"
|
||||||
|
path="deptId"
|
||||||
|
/>
|
||||||
|
<pro-input
|
||||||
|
title="邮箱"
|
||||||
|
path="email"
|
||||||
|
placeholder="example@domain.com"
|
||||||
|
/>
|
||||||
|
<pro-input
|
||||||
|
title="手机号"
|
||||||
|
path="phone"
|
||||||
|
placeholder="11位手机号"
|
||||||
|
/>
|
||||||
|
<pro-textarea
|
||||||
|
class="col-span-2"
|
||||||
|
title="备注"
|
||||||
|
path="remark"
|
||||||
|
/>
|
||||||
|
<pro-switch
|
||||||
|
title="用户状态"
|
||||||
|
path="userStatus"
|
||||||
|
:field-props="{ checkedValue: 1, uncheckedValue: 0 }"
|
||||||
|
/>
|
||||||
|
<pro-select
|
||||||
|
class="col-span-2"
|
||||||
|
title="角色"
|
||||||
|
path="roles"
|
||||||
|
:field-props="{ multiple: true, options: roleOptions }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</pro-modal-form>
|
||||||
|
</template>
|
@ -2,7 +2,7 @@
|
|||||||
import { createProSearchForm, useNDataTable } from 'pro-naive-ui'
|
import { createProSearchForm, useNDataTable } from 'pro-naive-ui'
|
||||||
import { deleteUser, getUserList } from '@/api'
|
import { deleteUser, getUserList } from '@/api'
|
||||||
import { createUserColumns, searchColumns } from './columns'
|
import { createUserColumns, searchColumns } from './columns'
|
||||||
import TableModal from './components/TableModal.vue'
|
import UserModal from './components/UserModal.vue'
|
||||||
|
|
||||||
const searchForm = createProSearchForm({
|
const searchForm = createProSearchForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@ -39,11 +39,11 @@ const tablecolumns = createUserColumns({
|
|||||||
onStatusChange: () => {},
|
onStatusChange: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getUserPage({ curent, pageSize }, formData) {
|
async function getUserPage({ current, pageSize }: any, formData: Entity.User[]) {
|
||||||
try {
|
try {
|
||||||
const { data } = await getUserList({
|
const { data } = await getUserList({
|
||||||
...formData,
|
...formData,
|
||||||
pageNum: curent,
|
pageNum: current,
|
||||||
pageSize,
|
pageSize,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
@ -121,6 +121,6 @@ const treeData = ref([
|
|||||||
</template>
|
</template>
|
||||||
</pro-data-table>
|
</pro-data-table>
|
||||||
</n-space>
|
</n-space>
|
||||||
<TableModal ref="modalRef" modal-name="用户" @success="refresh" />
|
<UserModal ref="modalRef" modal-name="用户" @success="refresh" />
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user