右侧配件列表更新

This commit is contained in:
2026-06-24 00:01:39 +08:00
parent aba361158e
commit 83153a4300
3 changed files with 786 additions and 512 deletions
+508 -252
View File
@@ -1,289 +1,289 @@
import { useEffect, useMemo, useState } from "react"
import { FirearmApi } from "@/api"
import slotNames from "@/constant/slots.json"
import tuningNames from "@/constant/tunings.json"
import { Firearm, ModificationRequest } from "@/types"
import { AutoComplete, Button, Card, Form, Input, InputNumber, Select, Space, Tag, message } from "antd"
import { EditOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons"
import AccessoryFormModal from "@/components/AccessoryFormModal"
// import { useEffect, useMemo, useState } from "react"
// import { FirearmApi } from "@/api"
// import slotNames from "@/constant/slots.json"
// import tuningNames from "@/constant/tunings.json"
// import { Firearm, ModificationRequest } from "@/types"
// import { AutoComplete, Button, Card, Form, Input, InputNumber, Select, Space, Tag, message } from "antd"
// import { EditOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons"
// import AccessoryFormModal from "@/components/AccessoryFormModal"
const slotOptions = slotNames.map((slotName) => ({ value: slotName }))
const tuningOptions = tuningNames.map((tuningName) => ({ value: tuningName }))
// const slotOptions = slotNames.map((slotName) => ({ value: slotName }))
// const tuningOptions = tuningNames.map((tuningName) => ({ value: tuningName }))
interface ModificationFormProps {
form: ReturnType<typeof Form.useForm<ModificationRequest>>[0]
onFinish: (values: ModificationRequest) => void
lockFirearmId?: number
}
// interface ModificationFormProps {
// form: ReturnType<typeof Form.useForm<ModificationRequest>>[0]
// onFinish: (values: ModificationRequest) => void
// lockFirearmId?: number
// }
interface AccessoryType {
slotName: string
accessoryName: string
tunings: Array<{ tuningName: string; tuningValue: number }>
}
// interface AccessoryType {
// slotName: string
// accessoryName: string
// tunings: Array<{ tuningName: string; tuningValue: number }>
// }
export default function ModificationForm({ form, onFinish, lockFirearmId }: ModificationFormProps) {
const [firearmOptions, setFirearmOptions] = useState<Array<{ value: number; label: string }>>([])
const [firearmLoading, setFirearmLoading] = useState(false)
// export default function ModificationForm({ form, onFinish, lockFirearmId }: ModificationFormProps) {
// const [firearmOptions, setFirearmOptions] = useState<Array<{ value: number; label: string }>>([])
// const [firearmLoading, setFirearmLoading] = useState(false)
// 配件弹窗相关状态
const [accessoryModalOpen, setAccessoryModalOpen] = useState(false)
const [editingAccessory, setEditingAccessory] = useState<AccessoryType | null>(null)
const [editingAccessoryIndex, setEditingAccessoryIndex] = useState<number | null>(null)
// // 配件弹窗相关状态
// const [accessoryModalOpen, setAccessoryModalOpen] = useState(false)
// const [editingAccessory, setEditingAccessory] = useState<AccessoryType | null>(null)
// const [editingAccessoryIndex, setEditingAccessoryIndex] = useState<number | null>(null)
useEffect(() => {
let active = true
// useEffect(() => {
// let active = true
async function loadAllFirearms() {
setFirearmLoading(true)
try {
const allFirearms: Firearm[] = []
let page = 0
let totalPages = 1
// async function loadAllFirearms() {
// setFirearmLoading(true)
// try {
// const allFirearms: Firearm[] = []
// let page = 0
// let totalPages = 1
while (page < totalPages) {
const paged = await FirearmApi.getFirearms({
page,
size: 100,
sortBy: "id",
direction: "ASC",
})
// while (page < totalPages) {
// const paged = await FirearmApi.getFirearms({
// page,
// size: 100,
// sortBy: "id",
// direction: "ASC",
// })
allFirearms.push(...paged.items)
totalPages = paged.totalPages
page += 1
}
// allFirearms.push(...paged.items)
// totalPages = paged.totalPages
// page += 1
// }
if (!active) {
return
}
// if (!active) {
// return
// }
setFirearmOptions(
allFirearms.map((firearm) => ({
value: firearm.id,
label: `${firearm.name}`,
}))
)
} finally {
if (active) {
setFirearmLoading(false)
}
}
}
// setFirearmOptions(
// allFirearms.map((firearm) => ({
// value: firearm.id,
// label: `${firearm.name}`,
// }))
// )
// } finally {
// if (active) {
// setFirearmLoading(false)
// }
// }
// }
void loadAllFirearms()
// void loadAllFirearms()
return () => {
active = false
}
}, [])
// return () => {
// active = false
// }
// }, [])
const mergedFirearmOptions = useMemo(() => {
if (
lockFirearmId === undefined ||
firearmOptions.some((option) => option.value === lockFirearmId)
) {
return firearmOptions
}
// const mergedFirearmOptions = useMemo(() => {
// if (
// lockFirearmId === undefined ||
// firearmOptions.some((option) => option.value === lockFirearmId)
// ) {
// return firearmOptions
// }
return [{ value: lockFirearmId, label: `武器 ID: ${lockFirearmId}` }, ...firearmOptions]
}, [firearmOptions, lockFirearmId])
// return [{ value: lockFirearmId, label: `武器 ID: ${lockFirearmId}` }, ...firearmOptions]
// }, [firearmOptions, lockFirearmId])
// 打开添加配件弹窗
const handleAddAccessory = () => {
setEditingAccessory(null)
setEditingAccessoryIndex(null)
setAccessoryModalOpen(true)
}
// // 打开添加配件弹窗
// const handleAddAccessory = () => {
// setEditingAccessory(null)
// setEditingAccessoryIndex(null)
// setAccessoryModalOpen(true)
// }
// 打开编辑配件弹窗
const handleEditAccessory = (index: number, accessory: AccessoryType) => {
setEditingAccessory(accessory)
setEditingAccessoryIndex(index)
setAccessoryModalOpen(true)
}
// // 打开编辑配件弹窗
// const handleEditAccessory = (index: number, accessory: AccessoryType) => {
// setEditingAccessory(accessory)
// setEditingAccessoryIndex(index)
// setAccessoryModalOpen(true)
// }
// 删除配件
const handleDeleteAccessory = (remove: (index: number) => void, index: number) => {
remove(index)
message.success("删除成功")
}
// // 删除配件
// const handleDeleteAccessory = (remove: (index: number) => void, index: number) => {
// remove(index)
// message.success("删除成功")
// }
// 配件弹窗确认回调
const handleAccessoryModalOk = (values: AccessoryType) => {
const accessories = form.getFieldValue("accessories") || []
// // 配件弹窗确认回调
// const handleAccessoryModalOk = (values: AccessoryType) => {
// const accessories = form.getFieldValue("accessories") || []
if (editingAccessoryIndex !== null) {
// 编辑模式:更新指定索引的数据
const updatedAccessories = [...accessories]
updatedAccessories[editingAccessoryIndex] = values
form.setFieldsValue({ accessories: updatedAccessories })
message.success("修改成功")
} else {
// 新增模式:添加新数据
form.setFieldsValue({ accessories: [...accessories, values] })
message.success("添加成功")
}
// if (editingAccessoryIndex !== null) {
// // 编辑模式:更新指定索引的数据
// const updatedAccessories = [...accessories]
// updatedAccessories[editingAccessoryIndex] = values
// form.setFieldsValue({ accessories: updatedAccessories })
// message.success("修改成功")
// } else {
// // 新增模式:添加新数据
// form.setFieldsValue({ accessories: [...accessories, values] })
// message.success("添加成功")
// }
setAccessoryModalOpen(false)
setEditingAccessory(null)
setEditingAccessoryIndex(null)
}
// setAccessoryModalOpen(false)
// setEditingAccessory(null)
// setEditingAccessoryIndex(null)
// }
// 配件弹窗取消回调
const handleAccessoryModalCancel = () => {
setAccessoryModalOpen(false)
setEditingAccessory(null)
setEditingAccessoryIndex(null)
}
// // 配件弹窗取消回调
// const handleAccessoryModalCancel = () => {
// setAccessoryModalOpen(false)
// setEditingAccessory(null)
// setEditingAccessoryIndex(null)
// }
return (
<>
<Form<ModificationRequest>
form={form}
layout="vertical"
onFinish={() => {
const allValues = form.getFieldsValue(true);
console.log('完整数据:', allValues);
onFinish(allValues);
}}
requiredMark={false}>
<Form.Item<ModificationRequest>
name="firearmId"
label="武器"
rules={[{ required: true, message: "请输入武器" }]}>
<Select<number>
className="w-full"
placeholder="请选择武器"
options={mergedFirearmOptions}
loading={firearmLoading}
disabled={lockFirearmId !== undefined}
showSearch={{
filterOption: (input, option) => {
const labelText = String(option?.label ?? "")
return labelText.toLowerCase().includes(input.toLowerCase())
},
}}
/>
</Form.Item>
// return (
// <>
// <Form<ModificationRequest>
// form={form}
// layout="vertical"
// onFinish={() => {
// const allValues = form.getFieldsValue(true);
// console.log('完整数据:', allValues);
// onFinish(allValues);
// }}
// requiredMark={false}>
// <Form.Item<ModificationRequest>
// name="firearmId"
// label="武器"
// rules={[{ required: true, message: "请输入武器" }]}>
// <Select<number>
// className="w-full"
// placeholder="请选择武器"
// options={mergedFirearmOptions}
// loading={firearmLoading}
// disabled={lockFirearmId !== undefined}
// showSearch={{
// filterOption: (input, option) => {
// const labelText = String(option?.label ?? "")
// return labelText.toLowerCase().includes(input.toLowerCase())
// },
// }}
// />
// </Form.Item>
<Form.Item<ModificationRequest>
name="name"
label="改装名称"
rules={[{ required: true, message: "请输入改装名称" }]}>
<Input placeholder="请输入改装名称" />
</Form.Item>
// <Form.Item<ModificationRequest>
// name="name"
// label="改装名称"
// rules={[{ required: true, message: "请输入改装名称" }]}>
// <Input placeholder="请输入改装名称" />
// </Form.Item>
<Form.Item<ModificationRequest>
name="code"
label="改枪码"
rules={[{ required: true, message: "请输入改枪码" }]}>
<Input placeholder="请输入改枪码" />
</Form.Item>
// <Form.Item<ModificationRequest>
// name="code"
// label="改枪码"
// rules={[{ required: true, message: "请输入改枪码" }]}>
// <Input placeholder="请输入改枪码" />
// </Form.Item>
<Form.Item<ModificationRequest> name="tags" label="标签">
<Select mode="tags" tokenSeparators={[",", " "]} placeholder="可选:输入后回车" />
</Form.Item>
// <Form.Item<ModificationRequest> name="tags" label="标签">
// <Select mode="tags" tokenSeparators={[",", " "]} placeholder="可选:输入后回车" />
// </Form.Item>
<Form.Item<ModificationRequest> name="author" label="作者">
<Input placeholder="可选:请输入作者" />
</Form.Item>
// <Form.Item<ModificationRequest> name="author" label="作者">
// <Input placeholder="可选:请输入作者" />
// </Form.Item>
<Form.Item<ModificationRequest> name="videoUrl" label="视频链接">
<Input placeholder="可选:请输入视频链接" />
</Form.Item>
// <Form.Item<ModificationRequest> name="videoUrl" label="视频链接">
// <Input placeholder="可选:请输入视频链接" />
// </Form.Item>
<Form.Item<ModificationRequest> name="note" label="备注">
<Input.TextArea rows={3} placeholder="可选:补充说明" />
</Form.Item>
// <Form.Item<ModificationRequest> name="note" label="备注">
// <Input.TextArea rows={3} placeholder="可选:补充说明" />
// </Form.Item>
{/* 改造后的配件列表 */}
<div className="flex flex-col gap-4">
// {/* 改造后的配件列表 */}
// <div className="flex flex-col gap-4">
<Form.Item<ModificationRequest> label="配件配置">
<Form.List name="accessories">
{(accessoryFields, { add, remove }) => (
<div className="flex flex-col gap-3">
{/* 配件标签列表 */}
<div className="flex flex-wrap gap-2">
{accessoryFields.length === 0 ? (
<div className="text-gray-400 text-sm py-2"></div>
) : (
accessoryFields.map((accessoryField, index) => {
const accessory = form.getFieldValue(["accessories", index]) as AccessoryType
return (
<Tag
key={accessoryField.key}
closable
onClose={(e) => {
e.preventDefault()
handleDeleteAccessory(remove, accessoryField.name)
}}
closeIcon={<DeleteOutlined />}
className="px-3 py-1.5 text-sm border hover:shadow-sm transition-all"
style={{ marginRight: 8, marginBottom: 8 }}
>
<Space size={6}>
<span className="font-medium text-gray-700">
{accessory?.slotName || "?"}
</span>
<span className="text-gray-400">·</span>
<span className="text-gray-800">
{accessory?.accessoryName || "未命名"}
</span>
{accessory?.tunings && accessory.tunings.length > 0 && (
<span className="text-blue-500 text-xs bg-blue-50 px-1.5 py-0.5 rounded">
{accessory.tunings.length}
</span>
)}
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={(e) => {
e.stopPropagation()
const currentAccessory = form.getFieldValue(["accessories", index])
handleEditAccessory(index, currentAccessory)
}}
className="text-blue-500 hover:text-blue-700 p-0"
style={{ marginLeft: 4 }}
/>
</Space>
</Tag>
)
})
)}
</div>
// <Form.Item<ModificationRequest> label="配件配置">
// <Form.List name="accessories">
// {(accessoryFields, { add, remove }) => (
// <div className="flex flex-col gap-3">
// {/* 配件标签列表 */}
// <div className="flex flex-wrap gap-2">
// {accessoryFields.length === 0 ? (
// <div className="text-gray-400 text-sm py-2">暂无配件,点击下方按钮添加</div>
// ) : (
// accessoryFields.map((accessoryField, index) => {
// const accessory = form.getFieldValue(["accessories", index]) as AccessoryType
// return (
// <Tag
// key={accessoryField.key}
// closable
// onClose={(e) => {
// e.preventDefault()
// handleDeleteAccessory(remove, accessoryField.name)
// }}
// closeIcon={<DeleteOutlined />}
// className="px-3 py-1.5 text-sm border hover:shadow-sm transition-all "
// style={{ marginRight: 8, marginBottom: 8 }}
// >
// <Space size={6}>
// <span className="font-medium text-[#2ee59d]">
// {accessory?.slotName || "?"}
// </span>
// <span className="text-gray-400">·</span>
// <span className="text-[#2ee59d]">
// {accessory?.accessoryName || "未命名"}
// </span>
// {accessory?.tunings && accessory.tunings.length > 0 && (
// <span className="text-blue-500 text-xs bg-blue-50 px-1.5 py-0.5 rounded">
// {accessory.tunings.length}项精校
// </span>
// )}
// <Button
// type="link"
// size="small"
// icon={<EditOutlined />}
// onClick={(e) => {
// e.stopPropagation()
// const currentAccessory = form.getFieldValue(["accessories", index])
// handleEditAccessory(index, currentAccessory)
// }}
// className="text-blue-500 hover:text-blue-700 p-0"
// style={{ marginLeft: 4 }}
// />
// </Space>
// </Tag>
// )
// })
// )}
// </div>
{/* 添加按钮 */}
<Button
type="dashed"
icon={<PlusOutlined />}
onClick={handleAddAccessory}
size="small"
className="w-full"
>
</Button>
</div>
)}
</Form.List>
</Form.Item>
</div>
// {/* 添加按钮 */}
// <Button
// type="dashed"
// icon={<PlusOutlined />}
// onClick={handleAddAccessory}
// size="small"
// className="w-full"
// >
// 添加配件
// </Button>
// </div>
// )}
// </Form.List>
// </Form.Item>
// </div>
</Form>
// </Form>
{/* 配件编辑弹窗 */}
<AccessoryFormModal
open={accessoryModalOpen}
onCancel={handleAccessoryModalCancel}
onOk={handleAccessoryModalOk}
initialData={editingAccessory}
editingIndex={editingAccessoryIndex}
/>
</>
)
}
// {/* 配件编辑弹窗 */}
// <AccessoryFormModal
// open={accessoryModalOpen}
// onCancel={handleAccessoryModalCancel}
// onOk={handleAccessoryModalOk}
// initialData={editingAccessory}
// editingIndex={editingAccessoryIndex}
// />
// </>
// )
// }
// import { useEffect, useMemo, useState } from "react"
// import { FirearmApi } from "@/api"
// import slotNames from "@/constant/slots.json"
@@ -501,3 +501,259 @@ export default function ModificationForm({ form, onFinish, lockFirearmId }: Modi
// </Form>
// )
// }
import { useEffect, useMemo, useState } from "react";
import { FirearmApi } from "@/api";
import slotNames from "@/constant/slots.json";
import tuningNames from "@/constant/tunings.json";
import { Firearm, ModificationRequest } from "@/types";
import { AutoComplete, Button, Card, Form, Input, InputNumber, Select, Space } from "antd";
interface ModificationFormProps {
form: ReturnType<typeof Form.useForm<ModificationRequest>>[0];
onFinish: (values: ModificationRequest) => void;
lockFirearmId?: number;
}
const slotOptions = slotNames.map((slotName) => ({ value: slotName }));
const tuningOptions = tuningNames.map((tuningName) => ({ value: tuningName }));
export default function ModificationForm({ form, onFinish, lockFirearmId }: ModificationFormProps) {
const [firearmOptions, setFirearmOptions] = useState<Array<{ value: number; label: string }>>([]);
const [firearmLoading, setFirearmLoading] = useState(false);
useEffect(() => {
let active = true;
async function loadAllFirearms() {
setFirearmLoading(true);
try {
const allFirearms: Firearm[] = [];
let page = 0;
let totalPages = 1;
while (page < totalPages) {
const paged = await FirearmApi.getFirearms({
page,
size: 100,
sortBy: "id",
direction: "ASC",
});
allFirearms.push(...paged.items);
totalPages = paged.totalPages;
page += 1;
}
if (!active) return;
setFirearmOptions(
allFirearms.map((firearm) => ({
value: firearm.id,
label: `${firearm.name}`,
}))
);
} finally {
if (active) {
setFirearmLoading(false);
}
}
}
void loadAllFirearms();
return () => {
active = false;
};
}, []);
const mergedFirearmOptions = useMemo(() => {
if (
lockFirearmId === undefined ||
firearmOptions.some((option) => option.value === lockFirearmId)
) {
return firearmOptions;
}
return [{ value: lockFirearmId, label: `武器 ID: ${lockFirearmId}` }, ...firearmOptions];
}, [firearmOptions, lockFirearmId]);
return (
<Form<ModificationRequest>
form={form}
layout="vertical"
onFinish={onFinish}
requiredMark={false}
className="max-w-full mx-auto"
>
{/* 强制不换行,左右并排 */}
<div className="flex flex-nowrap gap-6">
{/* 左侧:主要字段,固定宽度,控件宽度收窄 */}
<div className="flex-none w-[580px] space-y-4">
<Form.Item<ModificationRequest>
name="firearmId"
label="武器"
rules={[{ required: true, message: "请输入武器" }]}
>
<Select<number>
className="w-[580px]"
placeholder="请选择武器"
options={mergedFirearmOptions}
loading={firearmLoading}
disabled={lockFirearmId !== undefined}
showSearch={{
filterOption: (input, option) => {
const labelText = String(option?.label ?? "");
return labelText.toLowerCase().includes(input.toLowerCase());
},
}}
/>
</Form.Item>
<Form.Item<ModificationRequest>
name="name"
label="改装名称"
rules={[{ required: true, message: "请输入改装名称" }]}
>
<Input className="w-[580px]" placeholder="请输入改装名称" />
</Form.Item>
<Form.Item<ModificationRequest>
name="code"
label="改枪码"
rules={[{ required: true, message: "请输入改枪码" }]}
>
<Input className="w-[580px]" placeholder="请输入改枪码" />
</Form.Item>
<Form.Item<ModificationRequest> name="tags" label="标签">
<Select
mode="tags"
tokenSeparators={[",", " "]}
placeholder="可选:输入后回车"
className="w-[580px]"
/>
</Form.Item>
<Form.Item<ModificationRequest> name="author" label="作者">
<Input className="w-[580px]" placeholder="可选:请输入作者" />
</Form.Item>
<Form.Item<ModificationRequest> name="videoUrl" label="视频链接">
<Input className="w-[580px]" placeholder="可选:请输入视频链接" />
</Form.Item>
<Form.Item<ModificationRequest> name="note" label="备注">
<Input.TextArea className="w-[580px]" rows={3} placeholder="可选:补充说明" />
</Form.Item>
</div>
{/* 右侧:配件列表,固定宽度,卡片缩小,高度限制滚动 */}
<div className="flex-none w-[420px]">
<div className="max-h-[600px] overflow-y-auto pr-2 hide-scrollbar">
<Form.List name="accessories">
{(accessoryFields, { add: addAccessory, remove: removeAccessory }) => (
<div className="flex flex-col gap-4">
{accessoryFields.map((accessoryField) => (
<Card
key={accessoryField.key}
title={`配件 ${accessoryField.name + 1}`}
size="small"
style={{backgroundColor:'#1f1f1f'}}
extra={
<Button
danger
type="link"
size="small"
onClick={() => removeAccessory(accessoryField.name)}
>
</Button>
}
className="w-full [&_.ant-card-body]:!p-3" // 卡片内边距由默认24px缩小到12px
>
{/* 用容器控制表单项间距,由默认大间距缩小为4px */}
<div className="space-y-1">
<Form.Item
name={[accessoryField.name, "slotName"]}
label="槽位"
rules={[{ required: true, message: "请选择或输入槽位" }]}
className="!mb-1" // 表单项底部间距缩小
>
<AutoComplete options={slotOptions} placeholder="请选择或输入槽位" />
</Form.Item>
<Form.Item
name={[accessoryField.name, "accessoryName"]}
label="配件名称"
rules={[{ required: true, message: "请输入配件名称" }]}
className="!mb-1"
>
<Input placeholder="请输入配件名称" />
</Form.Item>
<Form.List name={[accessoryField.name, "tunings"]}>
{(tuningFields, { add: addTuning, remove: removeTuning }) => (
<div className="flex flex-col gap-2"> {/* 精校列表间距由3(12px)缩小为2(8px) */}
{tuningFields.map((tuningField) => (
<Space key={tuningField.key} align="start" className="w-full" size="small" wrap>
<Form.Item
name={[tuningField.name, "tuningName"]}
label="精校属性"
rules={[{ required: true, message: "请选择或输入精校属性" }]}
className="!mb-1"
>
<AutoComplete
options={tuningOptions}
placeholder="例如:后坐控制"
className="w-44" // 完全保留
/>
</Form.Item>
<Form.Item
name={[tuningField.name, "tuningValue"]}
label="精校值"
rules={[{ required: true, message: "请输入精校值" }]}
className="!mb-1"
>
<InputNumber className="w-32" placeholder="例如:0.35" /> {/* 完全保留 */}
</Form.Item>
<Button
type="link"
danger
className="mt-8" // 完全保留
onClick={() => removeTuning(tuningField.name)}
>
</Button>
</Space>
))}
<Button
type="dashed"
disabled={tuningFields.length >= 2}
onClick={() => addTuning({ tuningName: "", tuningValue: 0 })}
>
</Button>
</div>
)}
</Form.List>
</div>
</Card>
))}
{/* 添加配件按钮,保持在左侧(默认左对齐) */}
<Button
variant="solid"
color="lime"
className="self-start"
onClick={() => addAccessory({ slotName: "", accessoryName: "", tunings: [] })}
>
</Button>
</div>
)}
</Form.List>
</div>
</div>
</div>
</Form>
);
}
+18
View File
@@ -202,3 +202,21 @@ body {
.ant-collapse-body{
background: #1e1e1e;
}
.ant-modal-container{
width: 620px;
}
.hide-scrollbar {
max-height: 500px; /* 限制高度,触发滚动 */
overflow-y: auto; /* 允许滚动 */
/* 隐藏滚动条(Firefox 和 IE */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
/* 隐藏滚动条(Chrome/Safari/Edge 新版) */
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
+256 -256
View File
@@ -172,118 +172,105 @@ export default function FirearmsPage() {
</div>
</div>
<div className="mb-6">
{loading?(<div className="flex justify-center items-center h-64">
<Spin size="large" tip="加载中..." />
</div>):(
<Row gutter={[16, 16]}>
{firearms.map((firearm) => (
<Col key={firearm.id} xs={24} md={24} lg={24}>
{loading ? (<div className="flex justify-center items-center h-64">
<Spin size="large" tip="加载中..." />
</div>) : (
<Row gutter={[16, 16]}>
{firearms.map((firearm) => (
<Col key={firearm.id} xs={24} md={24} lg={24}>
<Card
className="hex-bg border-5px-#142c38"
extra={
user ? (
<div className="flex items-center gap-1">
<Button type="link" size="small" onClick={() => setEditingFirearm(firearm)}>
</Button>
<Popconfirm
title="确认删除武器"
description={`确定要删除 ${firearm.name} 吗?该操作不可撤销。`}
okText="删除"
cancelText="取消"
okButtonProps={{ danger: true, loading: deletingId === firearm.id }}
onConfirm={() => handleDelete(firearm)}>
<Button type="link" danger size="small" loading={deletingId === firearm.id}>
<Card
className="hex-bg border-5px-#142c38"
extra={
user ? (
<div className="flex items-center gap-1">
<Button type="link" size="small" onClick={() => setEditingFirearm(firearm)}>
</Button>
</Popconfirm>
</div>
) : null
}
variant="outlined"
style={{
height: '100%', display: 'flex', flexDirection: 'column'
}}
styles={{
root: {
border: '1px solid #313131'
},
header: {
borderBottom: '1px solid #303030',
},
body: {
flex: 1,
overflow: 'auto',
padding: '12px',
},
actions: {
flexShrink: 0,
background: '#1e1e1e',
display: 'flex'
<Popconfirm
title="确认删除武器"
description={`确定要删除 ${firearm.name} 吗?该操作不可撤销。`}
okText="删除"
cancelText="取消"
okButtonProps={{ danger: true, loading: deletingId === firearm.id }}
onConfirm={() => handleDelete(firearm)}>
<Button type="link" danger size="small" loading={deletingId === firearm.id}>
</Button>
</Popconfirm>
</div>
) : null
}
}}
actions={[
<div>
<Collapse
variant="outlined"
style={{
height: '100%', display: 'flex', flexDirection: 'column'
}}
styles={{
root: {
border: '1px solid #313131'
},
header: {
borderBottom: '1px solid #303030',
},
body: {
flex: 1,
overflow: 'auto',
padding: '12px',
expandIcon={() => null}
styles={
{
root: {
background: '#1e1e1e',
width: '100%'
},
actions: {
flexShrink: 0,
background: '#1e1e1e',
display: 'flex'
}
}}
actions={[
<div>
<Collapse
expandIcon={() => null}
styles={
{
root: {
background: '#1e1e1e',
width: '100%'
}
}
}
}
items={[{
key: '1',
label: (
<Button
variant="outlined"
styles={{
root: {
color: '#10E28C',
border: '1px solid #10E28C',
background: '#16343b96',
width: '20%',
}
}}
>
</Button>
),
children: <ModCodes firearmId={String(firearm.id)} />
}]}
/>
</div>,
]}>
<div className="flex flex-col gap-3">
<div className="lmr-container">
<div className="lmr-left">
<div style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
width: '100%'
}}>
<span style={{
display: 'inline-block',
backgroundColor: '#555555',
color: 'white',
padding: '4px 12px',
borderRadius: '4px',
fontSize: '14px',
fontWeight: '500',
letterSpacing: '0.5px'
items={[{
key: '1',
label: (
<Button
variant="outlined"
styles={{
root: {
color: '#10E28C',
border: '1px solid #10E28C',
background: '#16343b96',
width: '20%',
}
}}
>
</Button>
),
children: <ModCodes firearmId={String(firearm.id)} />
}]}
/>
</div>,
]}>
<div className="flex flex-col gap-3">
<div className="lmr-container">
<div className="lmr-left">
<div style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
width: '100%'
}}>
{firearm.name}
</span>
<span className="flex items-center justify-between"
style={{
<span style={{
display: 'inline-block',
backgroundColor: '#2d4f5796',
backgroundColor: '#555555',
color: 'white',
padding: '4px 12px',
borderRadius: '4px',
@@ -291,171 +278,184 @@ export default function FirearmsPage() {
fontWeight: '500',
letterSpacing: '0.5px'
}}>
{firearmTypeText[firearm.type]}
</span></div>
</div>
<div className="lmr-middle">
<div style={{
color: 'white',
padding: '4px 12px',
borderRadius: '4px',
fontSize: '14px',
fontWeight: '500',
letterSpacing: '0.5px'
}}>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))', // 最小宽度160px
gap: '12px',
}}>
{/* 武器输出等级 */}
<div style={{
border: '2px solid #10E28C',
backgroundColor: '#16343b96',
padding: '12px 16px',
borderRadius: '8px',
textAlign: 'center'
}}>
<div style={{
padding: '4px 0px',
{firearm.name}
</span>
<span className="flex items-center justify-between"
style={{
display: 'inline-block',
backgroundColor: '#2d4f5796',
color: 'white',
padding: '4px 12px',
borderRadius: '4px',
color: '#10E28C',
fontSize: '13px',
fontWeight: 500,
marginBottom: '6px',
whiteSpace: 'nowrap',
fontSize: '14px',
fontWeight: '500',
letterSpacing: '0.5px'
}}>
{firearmTypeText[firearm.type]}
</span></div>
</div>
<div className="lmr-middle">
<div style={{
color: 'white',
padding: '4px 12px',
borderRadius: '4px',
fontSize: '14px',
fontWeight: '500',
letterSpacing: '0.5px'
}}>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))', // 最小宽度160px
gap: '12px',
}}>
{/* 武器输出等级 */}
<div style={{
border: '2px solid #10E28C',
backgroundColor: '#16343b96',
padding: '12px 16px',
borderRadius: '8px',
textAlign: 'center'
}}>
<div style={{
padding: '4px 0px',
borderRadius: '4px',
color: '#10E28C',
fontSize: '13px',
fontWeight: 500,
marginBottom: '6px',
whiteSpace: 'nowrap',
textAlign: 'center'
}}>
</div>
<div style={{
fontSize: '16px',
fontWeight: 600,
color: '#ffffff'
}}>
{firearm.level}
</div>
</div>
{/* 子弹口径 */}
<div style={{
fontSize: '16px',
fontWeight: 600,
color: '#ffffff'
border: '2px solid #10E28C',
backgroundColor: '#16343b96',
padding: '12px 16px',
borderRadius: '8px',
textAlign: 'center'
}}>
{firearm.level}
<div style={{
padding: '4px 12px',
borderRadius: '4px',
color: '#10E28C',
fontSize: '13px',
fontWeight: 500,
marginBottom: '6px',
whiteSpace: 'nowrap'
}}>
</div>
<div style={{
fontSize: '16px',
fontWeight: 600,
color: '#ffffff'
}}>
{firearm.calibre}
</div>
</div>
{/* 每秒甲伤 */}
<div style={{
border: '2px solid #10E28C',
backgroundColor: '#16343b96',
padding: '12px 16px',
borderRadius: '8px',
textAlign: 'center'
}}>
<div style={{
padding: '4px 12px',
borderRadius: '4px',
color: '#10E28C',
fontSize: '13px',
fontWeight: 500,
marginBottom: '6px',
whiteSpace: 'nowrap'
}}>
</div>
<div style={{
fontSize: '16px',
fontWeight: 600,
color: '#ffffff'
}}>
{asDps(firearm.fireRate, firearm.armourDamage)}
</div>
</div>
{/* 每秒肉伤 */}
<div style={{
border: '2px solid #10E28C',
backgroundColor: '#16343b96',
padding: '12px 16px',
borderRadius: '8px',
textAlign: 'center'
}}>
<div style={{
padding: '4px 12px',
borderRadius: '4px',
color: '#10E28C',
fontSize: '13px',
fontWeight: 500,
marginBottom: '6px',
whiteSpace: 'nowrap'
}}>
</div>
<div style={{
fontSize: '16px',
fontWeight: 600,
color: '#ffffff'
}}>
{asDps(firearm.fireRate, firearm.bodyDamage)}
</div>
</div>
</div>
{/* 子弹口径 */}
<div style={{
border: '2px solid #10E28C',
backgroundColor: '#16343b96',
padding: '12px 16px',
borderRadius: '8px',
textAlign: 'center'
}}>
<div style={{
padding: '4px 12px',
borderRadius: '4px',
color: '#10E28C',
fontSize: '13px',
fontWeight: 500,
marginBottom: '6px',
whiteSpace: 'nowrap'
}}>
</div>
<div style={{
fontSize: '16px',
fontWeight: 600,
color: '#ffffff'
}}>
{firearm.calibre}
</div>
</div>
{/* 每秒甲伤 */}
<div style={{
border: '2px solid #10E28C',
backgroundColor: '#16343b96',
padding: '12px 16px',
borderRadius: '8px',
textAlign: 'center'
}}>
<div style={{
padding: '4px 12px',
borderRadius: '4px',
color: '#10E28C',
fontSize: '13px',
fontWeight: 500,
marginBottom: '6px',
whiteSpace: 'nowrap'
}}>
</div>
<div style={{
fontSize: '16px',
fontWeight: 600,
color: '#ffffff'
}}>
{asDps(firearm.fireRate, firearm.armourDamage)}
</div>
</div>
{/* 每秒肉伤 */}
<div style={{
border: '2px solid #10E28C',
backgroundColor: '#16343b96',
padding: '12px 16px',
borderRadius: '8px',
textAlign: 'center'
}}>
<div style={{
padding: '4px 12px',
borderRadius: '4px',
color: '#10E28C',
fontSize: '13px',
fontWeight: 500,
marginBottom: '6px',
whiteSpace: 'nowrap'
}}>
</div>
<div style={{
fontSize: '16px',
fontWeight: 600,
color: '#ffffff'
}}>
{asDps(firearm.fireRate, firearm.bodyDamage)}
</div>
</div>
</div>
</div>
<div className="lmr-right">
<Typography.Paragraph
style={{ marginBottom: 0 }}
type="secondary"
ellipsis={{
rows: 3,
tooltip: firearm.review
? {
title: <div style={{ whiteSpace: "pre-line" }}>{firearm.review}</div>,
placement: "topLeft",
}
: false,
}}
className="whitespace-pre-line">
{firearm.review || "暂无描述"}
</Typography.Paragraph>
</div>
</div>
<div className="lmr-right">
<Typography.Paragraph
style={{ marginBottom: 0 }}
type="secondary"
ellipsis={{
rows: 3,
tooltip: firearm.review
? {
title: <div style={{ whiteSpace: "pre-line" }}>{firearm.review}</div>,
placement: "topLeft",
}
: false,
}}
className="whitespace-pre-line">
{firearm.review || "暂无描述"}
</Typography.Paragraph>
</div>
</div>
</div>
</Card>
</Col>
))}
{firearms.length === 0 && (
<Col span={24}>
<Card>
<Typography.Text type="secondary"></Typography.Text>
</Card>
</Col>
)}
</Row>)}
</div>
</Card>
</Col>
))}
{firearms.length === 0 && (
<Col span={24}>
<Card>
<Typography.Text type="secondary"></Typography.Text>
</Card>
</Col>
)}
</Row>)}
</div>
<div className="flex justify-end">
<Pagination