3 Commits

6 changed files with 105 additions and 16 deletions
+1
View File
@@ -1,2 +1,3 @@
export * as FirearmApi from "./firearm-api" export * as FirearmApi from "./firearm-api"
export * as ModificationApi from "./modification-api" export * as ModificationApi from "./modification-api"
export * as TagApi from "./tag-api"
+8
View File
@@ -4,17 +4,25 @@ import { asUrlSearchParam } from "@/utils/query-param-utils.ts"
interface ModificationParams extends PageQueryParams { interface ModificationParams extends PageQueryParams {
firearmId?: string firearmId?: string
tags?: string[]
} }
export async function getModifications(params?: ModificationParams): Promise<Page<Modification>> { export async function getModifications(params?: ModificationParams): Promise<Page<Modification>> {
let uri = "/modifications" let uri = "/modifications"
const urlSearchParams = asUrlSearchParam(params) const urlSearchParams = asUrlSearchParam(params)
if (params?.firearmId) { if (params?.firearmId) {
urlSearchParams.append("firearmId", "" + params.firearmId) urlSearchParams.append("firearmId", "" + params.firearmId)
} }
if (params?.tags) {
params.tags.forEach((tag) => urlSearchParams.append("tags", tag))
}
if (urlSearchParams.size > 0) { if (urlSearchParams.size > 0) {
uri = uri.concat("?", urlSearchParams.toString()) uri = uri.concat("?", urlSearchParams.toString())
} }
const { data } = await WebClient.get<Page<Modification>>(uri) const { data } = await WebClient.get<Page<Modification>>(uri)
return data return data
} }
+18
View File
@@ -0,0 +1,18 @@
import { WebClient } from "@/shared/web-client"
export async function getTags(firearmId?: number): Promise<string[]> {
let uri = "/tags"
const urlSearchParam = new URLSearchParams()
if (firearmId) {
urlSearchParam.append("firearmId", "" + firearmId)
}
if (urlSearchParam.size > 0) {
uri = uri.concat("?", urlSearchParam.toString())
}
const { data } = await WebClient.get<string[]>(uri)
return data
}
+12
View File
@@ -18,6 +18,10 @@ const firearmTypeText: Record<FirearmType, string> = {
const allTypeValue = "ALL" const allTypeValue = "ALL"
type FirearmTypeFilter = FirearmType | typeof allTypeValue type FirearmTypeFilter = FirearmType | typeof allTypeValue
function asDps(fireRate: number, damage: number) {
return ((fireRate / 60) * damage).toFixed(2)
}
export default function FirearmsPage() { export default function FirearmsPage() {
const [page, setPage] = useState<number>(1) const [page, setPage] = useState<number>(1)
const [typeFilter, setTypeFilter] = useState<FirearmTypeFilter>(allTypeValue) const [typeFilter, setTypeFilter] = useState<FirearmTypeFilter>(allTypeValue)
@@ -79,6 +83,14 @@ export default function FirearmsPage() {
<strong></strong> <strong></strong>
{firearm.level} {firearm.level}
</Typography.Text> </Typography.Text>
<Typography.Text>
<strong></strong>
{asDps(firearm.fireRate, firearm.armourDamage)}
</Typography.Text>
<Typography.Text>
<strong></strong>
{asDps(firearm.fireRate, firearm.bodyDamage)}
</Typography.Text>
<Typography.Paragraph <Typography.Paragraph
style={{ marginBottom: 0 }} style={{ marginBottom: 0 }}
type="secondary" type="secondary"
+59 -13
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react" import { useEffect, useMemo, useState } from "react"
import { Link, useSearchParams } from "react-router-dom" import { Link, useSearchParams } from "react-router-dom"
import { Button, Card, Col, Pagination, Row, Space, Tag, Typography } from "antd" import { Button, Card, Col, Pagination, Row, Select, Space, Tag, Typography } from "antd"
import { ModificationApi } from "@/api" import { ModificationApi, TagApi } from "@/api"
import { Modification } from "@/types" import { Modification } from "@/types"
const pageSize = 12 const pageSize = 12
@@ -12,8 +12,17 @@ export default function ModCodesPage() {
const [page, setPage] = useState<number>(1) const [page, setPage] = useState<number>(1)
const [modifications, setModifications] = useState<Modification[]>([]) const [modifications, setModifications] = useState<Modification[]>([])
const [tagOptions, setTagOptions] = useState<string[]>([])
const [selectedTags, setSelectedTags] = useState<string[]>([])
const [total, setTotal] = useState<number>(0) const [total, setTotal] = useState<number>(0)
useEffect(() => {
const _firearmId = firearmId ? +firearmId : (void 0)
TagApi.getTags(_firearmId).then((tags) => {
setTagOptions(tags)
})
}, [])
useEffect(() => { useEffect(() => {
ModificationApi.getModifications({ ModificationApi.getModifications({
page: page - 1, page: page - 1,
@@ -21,30 +30,55 @@ export default function ModCodesPage() {
sortBy: "id", sortBy: "id",
direction: "ASC", direction: "ASC",
firearmId, firearmId,
tags: selectedTags,
}).then((pagedData) => { }).then((pagedData) => {
setModifications(pagedData.items) setModifications(pagedData.items)
setTotal(pagedData.totalElements) setTotal(pagedData.totalElements)
}) })
}, [page, firearmId]) }, [page, firearmId, selectedTags])
useEffect(() => { useEffect(() => {
setPage(1) setPage(1)
}, [firearmId]) }, [firearmId])
useEffect(() => {
setPage(1)
}, [selectedTags])
return ( return (
<> <>
<div className="mb-4 flex items-center justify-between gap-4"> <div className="mb-4 flex items-center justify-between gap-4">
<Typography.Title level={4} className="mb-0!"> <Typography.Title level={4} className="mb-0!">
</Typography.Title> </Typography.Title>
{firearmId && ( <Space wrap>
<Space> <span></span>
<Tag color="geekblue"> ID: {firearmId}</Tag> <Select<string[]>
mode="multiple"
allowClear
placeholder="请选择标签"
className="w-64"
value={selectedTags}
options={tagOptions.map((tag) => ({ value: tag, label: tag }))}
onChange={(values) => {
setSelectedTags(values)
}}
/>
{firearmId && <Tag color="geekblue"> ID: {firearmId}</Tag>}
{(firearmId || selectedTags.length > 0) && (
<Link to="/mod-codes"> <Link to="/mod-codes">
<Button type="link"></Button> <Button
type="link"
onClick={() => {
setSelectedTags([])
setPage(1)
}}
>
</Button>
</Link> </Link>
</Space>
)} )}
</Space>
</div> </div>
<div className="mb-6"> <div className="mb-6">
@@ -56,13 +90,22 @@ export default function ModCodesPage() {
variant="outlined" variant="outlined"
styles={{ styles={{
header: { minHeight: 56 }, header: { minHeight: 56 },
}} }}>
>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<Typography.Paragraph className="mb-0!" copyable={{ text: modification.code }}> <div className="flex items-center justify-between gap-2">
<span>
<strong></strong> <strong></strong>
<code className="bg-gray-400 px-2 py-1 rounded text-sm text-white">
{modification.code} {modification.code}
</Typography.Paragraph> </code>
</span>
<Button
type="text"
size="small"
onClick={() => navigator.clipboard.writeText(modification.code)}>
</Button>
</div>
<Typography.Text> <Typography.Text>
<strong></strong> <strong></strong>
@@ -77,7 +120,10 @@ export default function ModCodesPage() {
</div> </div>
)} )}
<Typography.Paragraph style={{ marginBottom: 0 }} type="secondary" ellipsis={{ rows: 3 }}> <Typography.Paragraph
style={{ marginBottom: 0 }}
type="secondary"
ellipsis={{ rows: 3 }}>
{modification.note || "暂无备注"} {modification.note || "暂无备注"}
</Typography.Paragraph> </Typography.Paragraph>
+4
View File
@@ -23,6 +23,10 @@ export interface Firearm {
name: string name: string
type: FirearmType type: FirearmType
level: string level: string
calibre: string
fireRate: number
armourDamage: number
bodyDamage: number
review: string review: string
} }