feat: 完成创建角色功能

This commit is contained in:
siujamo
2025-12-26 16:00:40 +08:00
parent 9139d0c451
commit 8aaad677b6
8 changed files with 185 additions and 16 deletions
+5
View File
@@ -1,6 +1,7 @@
import type { QueryRoleRequest } from "@/types/web/request"
import webClient from "@/service/web-client"
import type { PageResponse, RoleResponse } from "@/types/web/response"
import type { RoleFormValues } from "@/components/role-display-form"
export async function fetchRoles(
request: QueryRoleRequest | null
@@ -24,3 +25,7 @@ export async function fetchRoles(
const { data } = await webClient.get<RoleResponse>(`/roles?${params.toString()}`)
return data
}
export async function addRole(request: RoleFormValues) {
return await webClient.post("/roles", request)
}
@@ -0,0 +1,22 @@
import type { FormInstance } from "antd"
import RoleDisplayForm, { type RoleFormValues } from "@/components/role-display-form"
export interface AddRoleDialogueProps {
form: FormInstance<RoleFormValues>
}
export default function AddRoleDialogue({ form }: AddRoleDialogueProps) {
return (
<RoleDisplayForm
form={form}
initialValues={{
name: "",
code: "",
sort: 0,
description: null,
defaultValue: false,
status: "ACTIVE",
}}
/>
)
}
@@ -0,0 +1,11 @@
import type { FormInstance } from "antd"
import RoleDisplayForm, { type RoleFormValues } from "@/components/role-display-form"
export interface EditRoleDialogueProps {
form: FormInstance<RoleFormValues>
initialValues: RoleFormValues
}
export default function EditRoleDialogue({ form, initialValues }: EditRoleDialogueProps) {
return <RoleDisplayForm form={form} initialValues={initialValues} />
}
@@ -0,0 +1,66 @@
import { App, Form, type FormInstance, Input, InputNumber, Select, Switch } from "antd"
import { type Status, StatusOptions } from "@/types/constant"
/**
* Role form values.
*/
export interface RoleFormValues {
name: string
code: string
sort: number
defaultValue: boolean
description: string | null
status: Status
}
/**
* Component props.
*/
export interface RoleDisplayFormProps {
initialValues?: RoleFormValues
isEditing?: boolean
form: FormInstance<RoleFormValues>
isAdding?: boolean
}
export default function RoleDisplayForm({ initialValues, form }: RoleDisplayFormProps) {
return (
<Form<RoleFormValues>
form={form}
initialValues={initialValues}
layout="vertical"
labelAlign="right"
validateTrigger="onBlur">
<Form.Item<RoleFormValues>
label="角色名称"
name="name"
rules={[{ required: true, message: "角色名称不能为空" }]}>
<Input />
</Form.Item>
<Form.Item<RoleFormValues>
label="角色编码"
name="code"
rules={[
{ required: true, message: "角色编码不能为空" },
{ pattern: /^[a-z-]+$/, message: "角色编码格式错误,仅支持小写英文字母及 '-'" },
]}>
<Input />
</Form.Item>
<Form.Item<RoleFormValues>
label="排序编码"
name="sort"
rules={[
{ required: true, message: "排序不能为空" },
{ type: "number", min: 0, max: Number.MAX_VALUE, message: "排序必须是正数" },
]}>
<InputNumber />
</Form.Item>
<Form.Item<RoleFormValues> label="是否为默认角色" name="defaultValue">
<Switch />
</Form.Item>
<Form.Item<RoleFormValues> label="角色状态" name="status">
<Select<Status> options={StatusOptions} />
</Form.Item>
</Form>
)
}
+50 -7
View File
@@ -1,10 +1,10 @@
import { useEffect, useState } from "react"
import type { AxiosError } from "axios"
import axios, { type AxiosError } from "axios"
import { App, Button, Form, Input, Select, Space, Switch, Table } from "antd"
import type { Role } from "@/types/entity"
import { RoleApi } from "@/api"
import type { QueryRoleRequest } from "@/types/web/request"
import type { GeneralErrorResponse, RoleResponse } from "@/types/web/response"
import type { GeneralErrorResponse } from "@/types/web/response"
import {
DeleteOutlined,
ExportOutlined,
@@ -15,11 +15,15 @@ import {
} from "@ant-design/icons"
import type { QueryRoleForm } from "@/types/form"
import type { Status } from "@/types/constant"
import AddRoleDialogue from "@/components/add-role-dialogue"
import type { RoleFormValues } from "@/components/role-display-form"
export default function RolePage() {
const { message } = App.useApp()
const { message, modal } = App.useApp()
const [queryForm] = Form.useForm<QueryRoleForm>()
const [addRoleForm] = Form.useForm<RoleFormValues>()
const [editRoleForm] = Form.useForm<RoleFormValues>()
const [roles, setRoles] = useState<Role[]>([])
const [pageNum, setPageNum] = useState<number>(1)
const [pageSize, setPageSize] = useState<number>(10)
@@ -55,6 +59,43 @@ export default function RolePage() {
)
}
const onAddRoleFinish = async () => {
try {
const values = await addRoleForm.validateFields()
await RoleApi.addRole(values)
void message.success(`角色 ${values.name} 创建成功`)
return true
} catch (error: unknown) {
if (error instanceof Error && error.message.includes("Validation Failed")) {
return false
} else if (axios.isAxiosError<GeneralErrorResponse>(error)) {
void message.error(error.response?.data.message ?? "创建失败,请稍后再试")
}
return false
}
}
const handleAddRole = () => {
modal
.confirm({
title: "添加用户",
content: <AddRoleDialogue form={addRoleForm} />,
width: 600,
onOk: onAddRoleFinish,
})
.then(
() => {
addRoleForm.resetFields()
const formValues = queryForm.getFieldsValue()
queryRoles(pageNum, pageSize, formValues)
},
() => {
addRoleForm.resetFields()
console.error("用户取消添加角色")
}
)
}
useEffect(() => {
queryRoles(pageNum, pageSize, null)
}, [pageNum, pageSize])
@@ -94,19 +135,19 @@ export default function RolePage() {
/>
</Form.Item>
<Form.Item<QueryRoleForm>>
<Space.Compact>
<Space>
<Button color="primary" variant="solid" htmlType="submit" icon={<SearchOutlined />}>
</Button>
<Button color="orange" variant="solid" htmlType="reset" icon={<UndoOutlined />}>
</Button>
</Space.Compact>
</Space>
</Form.Item>
</Form>
<Space size={8}>
<Button variant="solid" type="primary" icon={<PlusOutlined />} onClick={() => {}}>
<Button variant="solid" type="primary" icon={<PlusOutlined />} onClick={handleAddRole}>
</Button>
<Button variant="solid" danger icon={<DeleteOutlined />}>
@@ -153,7 +194,9 @@ export default function RolePage() {
render: (role: Role) => (
<>
<Space.Compact>
<Button variant="solid"></Button>
<Button variant="solid" onClick={() => {}}>
</Button>
<Button variant="solid" danger>
</Button>
+12 -9
View File
@@ -1,9 +1,9 @@
import axios, { type AxiosError } from "axios"
import dayjs from "dayjs"
import store from "@/store"
import type { GeneralErrorResponse } from "@/types"
import { HttpStatus } from "@/constant"
import { logout } from "@/store/auth-slice"
import type { GeneralErrorResponse } from "@/types/web/response"
const webClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
@@ -25,14 +25,17 @@ webClient.interceptors.request.use(
}
)
webClient.interceptors.response.use((response) => {
return response
}, (error: unknown) => {
const err = error as AxiosError<GeneralErrorResponse>
if (err.response?.status == HttpStatus.UNAUTHORIZED) {
store.dispatch(logout())
webClient.interceptors.response.use(
(response) => {
return response
},
(error: unknown) => {
const err = error as AxiosError<GeneralErrorResponse>
if (err.response?.status == HttpStatus.UNAUTHORIZED) {
store.dispatch(logout())
}
return Promise.reject(error as AxiosError)
}
return Promise.reject(error as AxiosError)
})
)
export default webClient
+7
View File
@@ -12,3 +12,10 @@ export interface AntTreeSelectOption<T> {
selectable?: boolean
checkable?: boolean
}
export interface AntSelectOptionItem {
label: string
value: string
}
export type AntSelectOption = AntSelectOptionItem[]
+12
View File
@@ -1,6 +1,18 @@
import type { AntSelectOption } from "@/types/antd"
/**
* Status
*/
export type Status = "ACTIVE" | "INACTIVE"
export type UserStatus = Status | "LOCKED"
export const StatusOptions: AntSelectOption = [
{ label: "已启用", value: "ACTIVE" },
{ label: "已停用", value: "INACTIVE" },
]
export const UserStatusOptions: AntSelectOption = [
...StatusOptions,
{ label: "已禁用", value: "LOCKED" },
]