feat: add create and edit modals for modifications management

This commit is contained in:
2026-04-23 16:12:48 +08:00
parent abc4c68a0f
commit 2fc865ea57
6 changed files with 570 additions and 17 deletions
+123 -13
View File
@@ -1,7 +1,9 @@
import { useEffect, useMemo, useState } from "react"
import { App, Button, Card, Col, Pagination, Popconfirm, Row, Select, Space, Tag, Typography } from "antd"
import { useCallback, useEffect, useMemo, useState } from "react"
import { Link, useSearchParams } from "react-router-dom"
import { Button, Card, Col, Pagination, Row, Select, Space, Tag, Typography } from "antd"
import { ModificationApi, TagApi } from "@/api"
import ModificationCreateModal from "@/components/modification-create-modal"
import ModificationEditModal from "@/components/modification-edit-modal"
import { useAppSelector } from "@/store"
import { Modification } from "@/types"
@@ -9,24 +11,36 @@ const pageSize = 12
export default function ModCodesPage() {
const user = useAppSelector((state) => state.auth.user)
const { message } = App.useApp()
const [searchParams] = useSearchParams()
const firearmId = useMemo(() => searchParams.get("firearmId") || undefined, [searchParams])
const parsedFirearmId = useMemo(() => {
if (!firearmId) {
return undefined
}
const value = Number(firearmId)
return Number.isFinite(value) ? value : undefined
}, [firearmId])
const [page, setPage] = useState<number>(1)
const [modifications, setModifications] = useState<Modification[]>([])
const [tagOptions, setTagOptions] = useState<string[]>([])
const [selectedTags, setSelectedTags] = useState<string[]>([])
const [total, setTotal] = useState<number>(0)
const [deletingId, setDeletingId] = useState<number | null>(null)
const [createModalOpen, setCreateModalOpen] = useState(false)
const [editingModification, setEditingModification] = useState<Modification | null>(null)
useEffect(() => {
const _firearmId = firearmId ? +firearmId : (void 0)
const _firearmId = firearmId ? +firearmId : void 0
TagApi.getTags(_firearmId).then((tags) => {
setTagOptions(tags)
})
}, [])
}, [firearmId])
useEffect(() => {
ModificationApi.getModifications({
const loadModifications = useCallback(() => {
return ModificationApi.getModifications({
page: page - 1,
size: pageSize,
sortBy: "id",
@@ -39,6 +53,31 @@ export default function ModCodesPage() {
})
}, [page, firearmId, selectedTags])
useEffect(() => {
loadModifications()
}, [loadModifications])
async function handleDelete(modification: Modification) {
if (!user) {
return
}
setDeletingId(modification.id)
try {
await ModificationApi.removeModification(modification.id)
message.success("改枪码删除成功")
if (modifications.length === 1 && page > 1) {
setPage(page - 1)
} else {
loadModifications()
}
} catch {
message.error("改枪码删除失败,请稍后重试")
} finally {
setDeletingId(null)
}
}
useEffect(() => {
setPage(1)
}, [firearmId])
@@ -82,21 +121,39 @@ export default function ModCodesPage() {
</Link>
)}
</Space>
{user && <Button type="primary"></Button>}
{user && (
<Button type="primary" onClick={() => setCreateModalOpen(true)}>
</Button>
)}
</div>
</div>
<div className="mb-6">
<Row gutter={[16, 16]}>
{modifications.map((modification) => (
<Col key={modification.id} xs={24} md={12} lg={8}>
<Col key={modification.id} span={24}>
<Card
title={modification.name}
extra={
user ? (
<Button type="link" size="small">
</Button>
<div className="flex items-center gap-1">
<Button type="link" size="small" onClick={() => setEditingModification(modification)}>
</Button>
<Popconfirm
title="确认删除改枪码"
description={`确定要删除 ${modification.name} 吗?该操作不可撤销。`}
okText="删除"
cancelText="取消"
okButtonProps={{ danger: true, loading: deletingId === modification.id }}
onConfirm={() => handleDelete(modification)}
>
<Button type="link" danger size="small" loading={deletingId === modification.id}>
</Button>
</Popconfirm>
</div>
) : null
}
variant="outlined"
@@ -124,14 +181,45 @@ export default function ModCodesPage() {
{modification.author || "未知"}
</Typography.Text>
{modification.tags.length > 0 && (
{(modification.tags?.length || 0) > 0 && (
<div className="flex flex-wrap gap-2">
{modification.tags.map((tag) => (
{(modification.tags || []).map((tag) => (
<Tag key={`${modification.id}-${tag}`}>{tag}</Tag>
))}
</div>
)}
<div>
<Typography.Text strong></Typography.Text>
{(modification.accessories?.length || 0) > 0 ? (
<div className="mt-2 overflow-x-auto">
<div className="grid min-w-275 grid-cols-5 gap-2">
{(modification.accessories || []).map((accessory, accessoryIndex) => (
<div key={`${modification.id}-accessory-${accessoryIndex}`} className="rounded border border-gray-100 p-2">
<div className="flex items-center justify-between gap-2 rounded bg-gray-50 px-2 py-1">
<Tag color="blue" className="mr-0">{accessory.slotName || "未填写槽位"}</Tag>
<Tag className="mr-0">{accessory.accessoryName || "未填写配件"}</Tag>
</div>
{(accessory.tunings?.length || 0) > 0 ? (
<div className="mt-2 flex flex-wrap gap-1">
{accessory.tunings.map((tuning, tuningIndex) => (
<Tag key={`${modification.id}-${accessoryIndex}-tuning-${tuningIndex}`} color="geekblue">
{tuning.tuningName || "未命名"}: {tuning.tuningValue ?? "-"}
</Tag>
))}
</div>
) : null}
</div>
))}
</div>
</div>
) : (
<Typography.Text type="secondary" className="block mt-1">
</Typography.Text>
)}
</div>
<Typography.Paragraph
style={{ marginBottom: 0 }}
type="secondary"
@@ -173,6 +261,28 @@ export default function ModCodesPage() {
showSizeChanger={false}
/>
</div>
<ModificationCreateModal
open={createModalOpen}
defaultFirearmId={parsedFirearmId}
lockedFirearmId={parsedFirearmId}
onCancel={() => setCreateModalOpen(false)}
onSuccess={() => {
setCreateModalOpen(false)
loadModifications()
}}
/>
<ModificationEditModal
open={!!editingModification}
modification={editingModification}
lockedFirearmId={parsedFirearmId}
onCancel={() => setEditingModification(null)}
onSuccess={() => {
setEditingModification(null)
loadModifications()
}}
/>
</>
)
}