Compare commits

...

3 Commits

Author SHA1 Message Date
siujamo b0ccd8f832 feat: 完成角色编辑功能 2025-12-26 17:53:09 +08:00
siujamo 3b0bd56001 fix: 修复编辑表单打开后展示数据错误的问题 2025-12-26 17:49:56 +08:00
siujamo 8aaad677b6 feat: 完成创建角色功能 2025-12-26 16:00:40 +08:00
9 changed files with 255 additions and 17 deletions
+9
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,11 @@ 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)
}
export async function editRole(request: RoleFormValues) {
return await webClient.put("/roles", request)
}
@@ -0,0 +1,24 @@
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={{
id: null,
name: "",
code: "",
sort: 0,
description: null,
defaultValue: false,
status: "ACTIVE",
}}
mode="add"
/>
)
}
@@ -0,0 +1,12 @@
import type { FormInstance } from "antd"
import RoleDisplayForm, { type RoleFormValues } from "@/components/role-display-form"
import type { Role } from "@/types/entity"
export interface EditRoleDialogueProps {
form: FormInstance<RoleFormValues>
initialValues: RoleFormValues
}
export default function EditRoleDialogue({ form, initialValues }: EditRoleDialogueProps) {
return <RoleDisplayForm form={form} initialValues={initialValues} mode="edit" />
}
@@ -0,0 +1,82 @@
import { App, Form, type FormInstance, Input, InputNumber, Select, Switch } from "antd"
import { type Status, StatusOptions } from "@/types/constant"
import type { FormMode } from "@/types/form"
import { useEffect, useMemo } from "react"
/**
* Role form values.
*/
export interface RoleFormValues {
id: number | string | null
name: string
code: string
sort: number
defaultValue: boolean
description: string | null
status: Status
}
/**
* Component props.
*/
export interface RoleDisplayFormProps {
initialValues?: RoleFormValues
form: FormInstance<RoleFormValues>
mode: FormMode
}
export default function RoleDisplayForm({ initialValues, form, mode }: RoleDisplayFormProps) {
const isEditing = useMemo<boolean>(() => mode == "edit", [mode])
// Initialise form values
useEffect(() => {
if (initialValues) {
form.setFieldsValue(initialValues)
} else {
form.resetFields()
}
}, [initialValues, form])
return (
<Form<RoleFormValues>
form={form}
initialValues={initialValues}
layout="vertical"
labelAlign="right"
validateTrigger="onBlur">
<Form.Item<RoleFormValues> label="角色编号" hidden={!isEditing} name="id">
<Input disabled />
</Form.Item>
<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 disabled={isEditing}/>
</Form.Item>
<Form.Item<RoleFormValues> label="角色状态" name="status">
<Select<Status> options={StatusOptions} />
</Form.Item>
</Form>
)
}
+95 -8
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,17 @@ 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"
import EditRoleDialogue from "@/components/edit-role-dialogue"
import { addRole } from "@/api/role"
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)
@@ -36,7 +42,7 @@ export default function RolePage() {
RoleApi.fetchRoles(queryRoleRequest)
.then((response) => {
console.log("role response", response)
// console.log("role response", response)
setPageNum(response.pageable.pageNumber + 1)
setPageSize(response.pageable.pageSize)
setTotalElementCount(response.totalElements)
@@ -55,6 +61,85 @@ 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("用户取消添加角色")
}
)
.finally(() => {
addRoleForm.resetFields()
})
}
const onEditRoleFinish = async () => {
try {
const values = await editRoleForm.validateFields()
// console.log(values)
await RoleApi.editRole(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 handleEditRole = (role: Role) => {
modal
.confirm({
title: "修改用户",
content: <EditRoleDialogue form={editRoleForm} initialValues={role} />,
width: 600,
onOk: onEditRoleFinish,
})
.then(
() => {
const formValues = queryForm.getFieldsValue()
queryRoles(pageNum, pageSize, formValues)
},
() => {
console.error("用户取消添加角色")
}
)
.finally(() => {
editRoleForm.resetFields()
})
}
useEffect(() => {
queryRoles(pageNum, pageSize, null)
}, [pageNum, pageSize])
@@ -94,19 +179,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 +238,9 @@ export default function RolePage() {
render: (role: Role) => (
<>
<Space.Compact>
<Button variant="solid"></Button>
<Button variant="solid" onClick={() => handleEditRole(role)}>
</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" },
]
+2
View File
@@ -3,6 +3,8 @@ import type { CountryCode as RegionAbbreviation } from "libphonenumber-js"
import type { Status } from "@/types/constant"
import type { Dayjs } from "dayjs"
export type FormMode = "add" | "edit"
export interface UserFormValues extends Omit<
User,
"id" | "password" | "regionAbbreviation" | "departmentId" | "positionId" | "createdAt" | "updatedAt"