5 Commits

7 changed files with 110 additions and 16 deletions
+1
View File
@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build:tar": "pnpm build && tar -czf dist.tar.gz dist",
"lint": "eslint .",
"preview": "vite preview",
"deploy": "pnpm build && gh-pages -d dist",
+1
View File
@@ -1,2 +1,3 @@
export * as FirearmApi from "./firearm-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 {
firearmId?: string
tags?: string[]
}
export async function getModifications(params?: ModificationParams): Promise<Page<Modification>> {
let uri = "/modifications"
const urlSearchParams = asUrlSearchParam(params)
if (params?.firearmId) {
urlSearchParams.append("firearmId", "" + params.firearmId)
}
if (params?.tags) {
params.tags.forEach((tag) => urlSearchParams.append("tags", tag))
}
if (urlSearchParams.size > 0) {
uri = uri.concat("?", urlSearchParams.toString())
}
const { data } = await WebClient.get<Page<Modification>>(uri)
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
}
+16
View File
@@ -18,6 +18,10 @@ const firearmTypeText: Record<FirearmType, string> = {
const allTypeValue = "ALL"
type FirearmTypeFilter = FirearmType | typeof allTypeValue
function asDps(fireRate: number, damage: number) {
return ((fireRate / 60) * damage).toFixed(2)
}
export default function FirearmsPage() {
const [page, setPage] = useState<number>(1)
const [typeFilter, setTypeFilter] = useState<FirearmTypeFilter>(allTypeValue)
@@ -79,6 +83,18 @@ export default function FirearmsPage() {
<strong></strong>
{firearm.level}
</Typography.Text>
<Typography.Text>
<strong></strong>
{firearm.calibre}
</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
style={{ marginBottom: 0 }}
type="secondary"
+59 -13
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react"
import { Link, useSearchParams } from "react-router-dom"
import { Button, Card, Col, Pagination, Row, Space, Tag, Typography } from "antd"
import { ModificationApi } from "@/api"
import { Button, Card, Col, Pagination, Row, Select, Space, Tag, Typography } from "antd"
import { ModificationApi, TagApi } from "@/api"
import { Modification } from "@/types"
const pageSize = 12
@@ -12,8 +12,17 @@ export default function ModCodesPage() {
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)
useEffect(() => {
const _firearmId = firearmId ? +firearmId : (void 0)
TagApi.getTags(_firearmId).then((tags) => {
setTagOptions(tags)
})
}, [])
useEffect(() => {
ModificationApi.getModifications({
page: page - 1,
@@ -21,30 +30,55 @@ export default function ModCodesPage() {
sortBy: "id",
direction: "ASC",
firearmId,
tags: selectedTags,
}).then((pagedData) => {
setModifications(pagedData.items)
setTotal(pagedData.totalElements)
})
}, [page, firearmId])
}, [page, firearmId, selectedTags])
useEffect(() => {
setPage(1)
}, [firearmId])
useEffect(() => {
setPage(1)
}, [selectedTags])
return (
<>
<div className="mb-4 flex items-center justify-between gap-4">
<Typography.Title level={4} className="mb-0!">
</Typography.Title>
{firearmId && (
<Space>
<Tag color="geekblue"> ID: {firearmId}</Tag>
<Space wrap>
<span></span>
<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">
<Button type="link"></Button>
<Button
type="link"
onClick={() => {
setSelectedTags([])
setPage(1)
}}
>
</Button>
</Link>
</Space>
)}
</Space>
</div>
<div className="mb-6">
@@ -56,13 +90,22 @@ export default function ModCodesPage() {
variant="outlined"
styles={{
header: { minHeight: 56 },
}}
>
}}>
<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>
<code className="bg-gray-400 px-2 py-1 rounded text-sm text-white">
{modification.code}
</Typography.Paragraph>
</code>
</span>
<Button
type="text"
size="small"
onClick={() => navigator.clipboard.writeText(modification.code)}>
</Button>
</div>
<Typography.Text>
<strong></strong>
@@ -77,7 +120,10 @@ export default function ModCodesPage() {
</div>
)}
<Typography.Paragraph style={{ marginBottom: 0 }} type="secondary" ellipsis={{ rows: 3 }}>
<Typography.Paragraph
style={{ marginBottom: 0 }}
type="secondary"
ellipsis={{ rows: 3 }}>
{modification.note || "暂无备注"}
</Typography.Paragraph>
+4
View File
@@ -23,6 +23,10 @@ export interface Firearm {
name: string
type: FirearmType
level: string
calibre: string
fireRate: number
armourDamage: number
bodyDamage: number
review: string
}