From 745c98bc206416578c140ad23463289d04e775de Mon Sep 17 00:00:00 2001 From: zihluwang Date: Tue, 21 Apr 2026 23:40:00 +0800 Subject: [PATCH] feat: add create and edit modals for firearms management --- src/components/firearm-create-modal/index.tsx | 58 ++++++++++++ src/components/firearm-edit-modal/index.tsx | 67 ++++++++++++++ src/components/firearm-form/index.tsx | 91 +++++++++++++++++++ src/page/firearms/index.tsx | 83 +++++++++++++++-- 4 files changed, 291 insertions(+), 8 deletions(-) create mode 100644 src/components/firearm-create-modal/index.tsx create mode 100644 src/components/firearm-edit-modal/index.tsx create mode 100644 src/components/firearm-form/index.tsx diff --git a/src/components/firearm-create-modal/index.tsx b/src/components/firearm-create-modal/index.tsx new file mode 100644 index 0000000..100e571 --- /dev/null +++ b/src/components/firearm-create-modal/index.tsx @@ -0,0 +1,58 @@ +import { useState } from "react" +import { App, Form, Modal } from "antd" +import { FirearmApi } from "@/api" +import FirearmForm from "@/components/firearm-form" +import { AddFirearmRequest, Firearm } from "@/types" + +interface FirearmCreateModalProps { + open: boolean + onCancel: () => void + onSuccess: (firearm: Firearm) => void +} + +function normalizeRequest(values: AddFirearmRequest): AddFirearmRequest { + return { + ...values, + review: values.review?.trim() || null, + } +} + +export default function FirearmCreateModal({ open, onCancel, onSuccess }: FirearmCreateModalProps) { + const { message } = App.useApp() + const [form] = Form.useForm() + const [loading, setLoading] = useState(false) + + async function onFinish(values: AddFirearmRequest) { + setLoading(true) + try { + const firearm = await FirearmApi.addFirearm(normalizeRequest(values)) + message.success("武器创建成功") + form.resetFields() + onSuccess(firearm) + } catch { + message.error("武器创建失败,请稍后重试") + } finally { + setLoading(false) + } + } + + return ( + form.submit()} + okText="创建" + cancelText="取消" + confirmLoading={loading} + destroyOnHidden + afterOpenChange={(visible) => { + if (!visible) { + form.resetFields() + } + }}> + + + ) +} + diff --git a/src/components/firearm-edit-modal/index.tsx b/src/components/firearm-edit-modal/index.tsx new file mode 100644 index 0000000..c0c1b49 --- /dev/null +++ b/src/components/firearm-edit-modal/index.tsx @@ -0,0 +1,67 @@ +import { useEffect, useState } from "react" +import { App, Form, Modal } from "antd" +import { FirearmApi } from "@/api" +import FirearmForm from "@/components/firearm-form" +import { AddFirearmRequest, Firearm } from "@/types" + +interface FirearmEditModalProps { + open: boolean + firearm: Firearm | null + onCancel: () => void + onSuccess: (firearm: Firearm) => void +} + +function normalizeRequest(values: AddFirearmRequest): AddFirearmRequest { + return { + ...values, + review: values.review?.trim() || null, + } +} + +export default function FirearmEditModal({ open, firearm, onCancel, onSuccess }: FirearmEditModalProps) { + const { message } = App.useApp() + const [form] = Form.useForm() + const [loading, setLoading] = useState(false) + + useEffect(() => { + if (!open || !firearm) { + return + } + + const { id: _id, ...editableValues } = firearm + form.setFieldsValue(editableValues) + }, [open, firearm, form]) + + async function onFinish(values: AddFirearmRequest) { + if (!firearm) { + return + } + + setLoading(true) + try { + const updated = await FirearmApi.editFirearm(firearm.id, normalizeRequest(values)) + message.success("武器更新成功") + onSuccess(updated) + } catch { + message.error("武器更新失败,请稍后重试") + } finally { + setLoading(false) + } + } + + return ( + form.submit()} + okText="保存" + cancelText="取消" + confirmLoading={loading} + destroyOnHidden + > + + + ) +} + diff --git a/src/components/firearm-form/index.tsx b/src/components/firearm-form/index.tsx new file mode 100644 index 0000000..476218e --- /dev/null +++ b/src/components/firearm-form/index.tsx @@ -0,0 +1,91 @@ +import { Form, Input, InputNumber, Select } from "antd" +import { AddFirearmRequest, FirearmType } from "@/types" + +const firearmTypeText: Record = { + RIFLE: "步枪", + SUB_MACHINE_GUN: "冲锋枪", + SHOTGUN: "霰弹枪", + LIGHT_MACHINE_GUN: "轻机枪", + DESIGNATED_MARKSMAN_RIFLE: "射手步枪", + SNIPER_RIFLE: "狙击步枪", + PISTOL: "手枪", + SPECIAL: "特殊", +} + +interface FirearmFormProps { + form: ReturnType>[0] + onFinish: (values: AddFirearmRequest) => void +} + +export default function FirearmForm({ form, onFinish }: FirearmFormProps) { + return ( + form={form} layout="vertical" onFinish={onFinish} requiredMark={false}> + + name="name" + label="武器名称" + rules={[{ required: true, message: "请输入武器名称" }]} + > + + + + + name="type" + label="武器类型" + rules={[{ required: true, message: "请选择武器类型" }]} + > + + + + + name="calibre" + label="子弹口径" + rules={[{ required: true, message: "请输入子弹口径" }]} + > + + + + + name="fireRate" + label="射速(每分钟发数)" + rules={[{ required: true, message: "请输入射速" }]} + > + + + + + name="armourDamage" + label="甲伤" + rules={[{ required: true, message: "请输入甲伤" }]} + > + + + + + name="bodyDamage" + label="肉伤" + rules={[{ required: true, message: "请输入肉伤" }]} + > + + + + name="review" label="描述"> + + + + ) +} + diff --git a/src/page/firearms/index.tsx b/src/page/firearms/index.tsx index 611041f..1824770 100644 --- a/src/page/firearms/index.tsx +++ b/src/page/firearms/index.tsx @@ -1,9 +1,11 @@ -import { useEffect, useState } from "react" +import { useCallback, useEffect, useState } from "react" import { Link } from "react-router-dom" import { FirearmApi } from "@/api" +import FirearmCreateModal from "@/components/firearm-create-modal" +import FirearmEditModal from "@/components/firearm-edit-modal" import { useAppSelector } from "@/store" import { Firearm, FirearmType } from "@/types" -import { Button, Card, Col, Pagination, Row, Select, Tag, Typography } from "antd" +import { Button, Card, Col, Pagination, Popconfirm, Row, Select, Tag, Typography, App } from "antd" const firearmTypeText: Record = { RIFLE: "步枪", @@ -25,13 +27,17 @@ function asDps(fireRate: number, damage: number) { export default function FirearmsPage() { const user = useAppSelector((state) => state.auth.user) + const { message } = App.useApp() const [page, setPage] = useState(1) const [typeFilter, setTypeFilter] = useState(allTypeValue) const [firearms, setFirearms] = useState([]) const [total, setTotal] = useState(0) + const [createModalOpen, setCreateModalOpen] = useState(false) + const [editingFirearm, setEditingFirearm] = useState(null) + const [deletingId, setDeletingId] = useState(null) - useEffect(() => { - FirearmApi.getFirearms({ + const loadFirearms = useCallback(() => { + return FirearmApi.getFirearms({ page: page - 1, size: 12, sortBy: "id", @@ -43,9 +49,37 @@ export default function FirearmsPage() { }) }, [page, typeFilter]) + useEffect(() => { + loadFirearms() + }, [loadFirearms]) + + async function handleDelete(firearm: Firearm) { + setDeletingId(firearm.id) + try { + await FirearmApi.removeFirearm(firearm.id) + message.success("武器删除成功") + if (firearms.length === 1 && page > 1) { + setPage(page - 1) + } else { + loadFirearms() + } + } catch { + message.error("武器删除失败,请稍后重试") + } finally { + setDeletingId(null) + } + } + return ( <> -
+
+
+ {user && ( + + )} +
className="w-full sm:w-64" value={typeFilter} @@ -70,9 +104,23 @@ export default function FirearmsPage() { title={firearm.name} extra={ user ? ( - +
+ + handleDelete(firearm)} + > + + +
) : null } variant="outlined" @@ -136,6 +184,25 @@ export default function FirearmsPage() { showSizeChanger={false} />
+ + setCreateModalOpen(false)} + onSuccess={() => { + setCreateModalOpen(false) + loadFirearms() + }} + /> + + setEditingFirearm(null)} + onSuccess={() => { + setEditingFirearm(null) + loadFirearms() + }} + /> ) }