diff --git a/src/api/auth-api.ts b/src/api/auth-api.ts new file mode 100644 index 0000000..1727cde --- /dev/null +++ b/src/api/auth-api.ts @@ -0,0 +1,13 @@ +import { LoginRequest, User } from "@/types" +import { WebClient } from "@/shared/web-client" + +export async function login(loginRequest: LoginRequest): Promise { + const { data } = await WebClient.post("/auth/login", { + ...loginRequest, + }) + return data +} + +export async function logout() { + await WebClient.get("/auth/logout") +} diff --git a/src/api/firearm-api.ts b/src/api/firearm-api.ts index f2f8e0b..9b0bbba 100644 --- a/src/api/firearm-api.ts +++ b/src/api/firearm-api.ts @@ -1,4 +1,4 @@ -import { Direction, Firearm, FirearmType, Page, PageQueryParams } from "@/types" +import { AddFirearmRequest, Direction, Firearm, FirearmType, Page, PageQueryParams } from "@/types" import { WebClient } from "@/shared/web-client" import { asUrlSearchParam } from "@/utils/query-param-utils.ts" @@ -36,3 +36,21 @@ export async function getFirearm(id: number): Promise { const { data } = await WebClient.get(`/firearms/${id}`) return data } + +/** + * 新建武器 + * @param request + */ +export async function addFirearm(request: AddFirearmRequest): Promise { + const { data } = await WebClient.post("/firearms", request) + return data +} + +export async function editFirearm(id: number, request: AddFirearmRequest): Promise { + const { data } = await WebClient.put(`/firearms/${id}`, request) + return data +} + +export async function removeFirearm(id: number) { + await WebClient.delete(`/firearms/${id}`) +} diff --git a/src/api/index.ts b/src/api/index.ts index eb0551b..edb4561 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,3 +1,4 @@ export * as FirearmApi from "./firearm-api" export * as ModificationApi from "./modification-api" export * as TagApi from "./tag-api" +export * as AuthApi from "./auth-api" diff --git a/src/api/modification-api.ts b/src/api/modification-api.ts index 1d95436..84254c2 100644 --- a/src/api/modification-api.ts +++ b/src/api/modification-api.ts @@ -1,4 +1,4 @@ -import { Modification, Page, PageQueryParams } from "@/types" +import { Modification, ModificationRequest, Page, PageQueryParams } from "@/types" import { WebClient } from "@/shared/web-client" import { asUrlSearchParam } from "@/utils/query-param-utils.ts" @@ -30,4 +30,38 @@ export async function getModifications(params?: ModificationParams): Promise { const { data } = await WebClient.get(`/modifications/${id}`) return data +} + +export async function addModification(modification: ModificationRequest): Promise { + const { data } = await WebClient.post("/modifications", modification) + return data +} + +export async function addModifications( + modifications: ModificationRequest[] +): Promise { + const { data } = await WebClient.post("/modifications/batch", { + modifications, + }) + return data +} + +export async function editModification( + id: number, + modification: ModificationRequest +): Promise { + const { data } = await WebClient.put(`/modifications/${id}`, modification) + return data +} + +export async function removeModification( + id: number +): Promise { + await WebClient.delete(`/modifications/${id}`) +} + +export async function removeModifications(ids: number[]) { + const urlSearchParams = new URLSearchParams() + ids.forEach((id) => urlSearchParams.append("ids", "" + id)) + await WebClient.delete(`/modifications/batch-delete?${urlSearchParams.toString()}`) } \ No newline at end of file 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..2095383 --- /dev/null +++ b/src/components/firearm-form/index.tsx @@ -0,0 +1,101 @@ +import { Form, Input, InputNumber, Select } from "antd" +import { AddFirearmRequest, FirearmType } from "@/types" +import calibres from "@/constant/calibres.json" + +const firearmTypeText: Record = { + RIFLE: "步枪", + SUB_MACHINE_GUN: "冲锋枪", + SHOTGUN: "霰弹枪", + LIGHT_MACHINE_GUN: "轻机枪", + DESIGNATED_MARKSMAN_RIFLE: "射手步枪", + SNIPER_RIFLE: "狙击步枪", + PISTOL: "手枪", + SPECIAL: "特殊", +} + +const calibreOptions = calibres.map((calibre) => ({ + value: calibre, + label: calibre, +})) + +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="code" + label="改枪码" + rules={[{ required: true, message: "请输入改枪码" }]}> + + + + name="tags" label="标签"> + + + + name="videoUrl" label="视频链接"> + + + + name="note" label="备注"> + + + + + {(accessoryFields, { add: addAccessory, remove: removeAccessory }) => ( +
+ {accessoryFields.map((accessoryField) => ( + removeAccessory(accessoryField.name)}> + 删除配件 + + }> + + + + + + + + + + {(tuningFields, { add: addTuning, remove: removeTuning }) => ( +
+ {tuningFields.map((tuningField) => ( + + + + + + + + + + ))} + +
+ )} +
+
+ ))} + +
+ )} +
+ + ) +} + + + diff --git a/src/constant/calibres.json b/src/constant/calibres.json new file mode 100644 index 0000000..b1f47ad --- /dev/null +++ b/src/constant/calibres.json @@ -0,0 +1,23 @@ +[ + ".338 Lap Mag", + ".357 Magnum", + ".45 ACP", + ".50 AE", + ".50 BMG", + "12 Gauge", + "12.7x55mm", + "5.45x39mm", + "5.56x45mm", + "5.7x28mm", + "5.8x42mm", + "6.8x51mm", + "7.62x39mm", + "7.62x51mm", + "7.62x54mm", + "9x19mm", + "9x39mm", + "4.6x30mm", + ".300 BLK", + "箭矢", + "45-70 Govt" +] \ No newline at end of file diff --git a/src/constant/slots.json b/src/constant/slots.json new file mode 100644 index 0000000..d5716ec --- /dev/null +++ b/src/constant/slots.json @@ -0,0 +1,23 @@ +[ + "枪口", + "左导轨", + "右导轨", + "枪管", + "左贴片", + "右贴片", + "上导轨", + "上贴片", + "下导轨", + "瞄准镜", + "战术设备", + "增高座瞄具", + "侧瞄具", + "枪托", + "枪托套件", + "后握把", + "前握把", + "导轨脚架", + "弹匣座", + "弹匣", + "托腮板" +] diff --git a/src/init/index.ts b/src/init/index.ts deleted file mode 100644 index fc6e552..0000000 --- a/src/init/index.ts +++ /dev/null @@ -1 +0,0 @@ -import "./dayjs" \ No newline at end of file diff --git a/src/layout/hero-layout/index.tsx b/src/layout/hero-layout/index.tsx index 113005e..6607963 100644 --- a/src/layout/hero-layout/index.tsx +++ b/src/layout/hero-layout/index.tsx @@ -1,6 +1,10 @@ import { Outlet, Link } from "react-router-dom" import { useMemo } from "react" import dayjs from "dayjs" +import { Dropdown } from "antd" +import { AuthApi } from "@/api" +import { useAppDispatch, useAppSelector } from "@/store" +import { clearCurrentUser } from "@/store/auth-slice" /** * Main application component that serves as the root layout. @@ -8,6 +12,16 @@ import dayjs from "dayjs" */ export default function HeroLayout() { const today = useMemo(() => dayjs(), []) + const user = useAppSelector((state) => state.auth.user) + const dispatch = useAppDispatch() + + async function handleLogout() { + try { + await AuthApi.logout() + } finally { + dispatch(clearCurrentUser()) + } + } return (
@@ -33,6 +47,32 @@ export default function HeroLayout() { > 改枪码 + {user ? ( + + + {user.username} + + + ) : ( + + 登录 + + )} = { RIFLE: "步枪", @@ -23,13 +26,18 @@ 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", @@ -41,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} @@ -66,6 +102,27 @@ export default function FirearmsPage() { + + handleDelete(firearm)} + > + + +
+ ) : null + } variant="outlined" styles={{ header: { minHeight: 56 }, @@ -127,6 +184,25 @@ export default function FirearmsPage() { showSizeChanger={false} />
+ + setCreateModalOpen(false)} + onSuccess={() => { + setCreateModalOpen(false) + loadFirearms() + }} + /> + + setEditingFirearm(null)} + onSuccess={() => { + setEditingFirearm(null) + loadFirearms() + }} + /> ) } diff --git a/src/page/login/index.tsx b/src/page/login/index.tsx new file mode 100644 index 0000000..2b4c45e --- /dev/null +++ b/src/page/login/index.tsx @@ -0,0 +1,67 @@ +import { useState } from "react" +import { useNavigate } from "react-router-dom" +import { App, Button, Card, Form, Input, Typography } from "antd" +import { AuthApi } from "@/api" +import { useAppDispatch } from "@/store" +import { setCurrentUser } from "@/store/auth-slice" +import { LoginRequest } from "@/types" + +export default function LoginPage() { + const navigate = useNavigate() + const { message } = App.useApp() + const dispatch = useAppDispatch() + const [loading, setLoading] = useState(false) + + async function onFinish(values: LoginRequest) { + setLoading(true) + try { + const user = await AuthApi.login(values) + dispatch(setCurrentUser(user)) + message.success(`欢迎回来,${user.username}`) + navigate("/firearms") + } catch { + message.error("登录失败,请检查帐号或密码") + } finally { + setLoading(false) + } + } + + return ( +
+
+ + + 登录 + + + 使用你的帐号登录后即可继续操作 + + + layout="vertical" onFinish={onFinish} requiredMark={false}> + + name="principle" + label="帐号" + rules={[{ required: true, message: "请输入帐号" }]} + > + + + + + name="credential" + label="密码" + rules={[{ required: true, message: "请输入密码" }]} + > + + + + + + + + +
+
+ ) +} diff --git a/src/page/mod-codes/index.tsx b/src/page/mod-codes/index.tsx index c18f29b..305e967 100644 --- a/src/page/mod-codes/index.tsx +++ b/src/page/mod-codes/index.tsx @@ -1,30 +1,46 @@ -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" 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(1) const [modifications, setModifications] = useState([]) const [tagOptions, setTagOptions] = useState([]) const [selectedTags, setSelectedTags] = useState([]) const [total, setTotal] = useState(0) + const [deletingId, setDeletingId] = useState(null) + const [createModalOpen, setCreateModalOpen] = useState(false) + const [editingModification, setEditingModification] = useState(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", @@ -37,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]) @@ -47,46 +88,74 @@ export default function ModCodesPage() { return ( <> -
+
改枪码列表 - - 标签: - - mode="multiple" - allowClear - placeholder="请选择标签" - className="w-64" - value={selectedTags} - options={tagOptions.map((tag) => ({ value: tag, label: tag }))} - onChange={(values) => { - setSelectedTags(values) - }} - /> - {firearmId && 武器 ID: {firearmId}} - {(firearmId || selectedTags.length > 0) && ( - - - +
+ + 标签: + + mode="multiple" + allowClear + placeholder="请选择标签" + className="w-64" + value={selectedTags} + options={tagOptions.map((tag) => ({ value: tag, label: tag }))} + onChange={(values) => { + setSelectedTags(values) + }} + /> + {firearmId && 武器 ID: {firearmId}} + {(firearmId || selectedTags.length > 0) && ( + + + + )} + + {user && ( + )} - +
{modifications.map((modification) => ( - + + + handleDelete(modification)} + > + + +
+ ) : null + } variant="outlined" styles={{ header: { minHeight: 56 }, @@ -112,14 +181,45 @@ export default function ModCodesPage() { {modification.author || "未知"} - {modification.tags.length > 0 && ( + {(modification.tags?.length || 0) > 0 && (
- {modification.tags.map((tag) => ( + {(modification.tags || []).map((tag) => ( {tag} ))}
)} +
+ 配件配置: + {(modification.accessories?.length || 0) > 0 ? ( +
+
+ {(modification.accessories || []).map((accessory, accessoryIndex) => ( +
+
+ {accessory.slotName || "未填写槽位"} + {accessory.accessoryName || "未填写配件"} +
+ {(accessory.tunings?.length || 0) > 0 ? ( +
+ {accessory.tunings.map((tuning, tuningIndex) => ( + + {tuning.tuningName || "未命名"}: {tuning.tuningValue ?? "-"} + + ))} +
+ ) : null} +
+ ))} +
+
+ ) : ( + + 暂无配件信息 + + )} +
+
+ + setCreateModalOpen(false)} + onSuccess={() => { + setCreateModalOpen(false) + void loadModifications() + }} + /> + + setEditingModification(null)} + onSuccess={() => { + setEditingModification(null) + void loadModifications() + }} + /> ) } diff --git a/src/router/index.tsx b/src/router/index.tsx index b506a3c..1a23589 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -1,6 +1,7 @@ import { ComponentType } from "react" import { createBrowserRouter } from "react-router-dom" import ErrorPage from "@/components/error-page" +import EmptyLayout from "@/layout/empty-layout" import HeroLayout from "@/layout/hero-layout" function lazy }>(importer: () => Promise) { @@ -37,6 +38,16 @@ const router = createBrowserRouter( }, ], }, + { + element: , + errorElement: , + children: [ + { + path: "login", + lazy: lazy(() => import("@/page/login")), + }, + ], + }, ], { basename: "/", diff --git a/src/init/dayjs/index.ts b/src/shared/dayjs/index.ts similarity index 66% rename from src/init/dayjs/index.ts rename to src/shared/dayjs/index.ts index 35a84ed..42e67bc 100644 --- a/src/init/dayjs/index.ts +++ b/src/shared/dayjs/index.ts @@ -3,4 +3,4 @@ import duration from "dayjs/plugin/duration" dayjs.extend(duration) -console.log("Global Dayjs plugins initialised.") \ No newline at end of file +export default dayjs diff --git a/src/shared/web-client/index.ts b/src/shared/web-client/index.ts index b6b2081..b2fc077 100644 --- a/src/shared/web-client/index.ts +++ b/src/shared/web-client/index.ts @@ -1,9 +1,10 @@ import axios from "axios" -import dayjs from "dayjs" +import dayjs from "@/shared/dayjs" const WebClient = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: dayjs.duration({ seconds: 10 }).asMilliseconds(), + withCredentials: true }) export { WebClient } diff --git a/src/store/auth-slice.ts b/src/store/auth-slice.ts new file mode 100644 index 0000000..71dc980 --- /dev/null +++ b/src/store/auth-slice.ts @@ -0,0 +1,26 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit" +import { User } from "@/types" + +interface AuthState { + user: User | null +} + +const initialState: AuthState = { + user: null, +} + +const authSlice = createSlice({ + name: "auth", + initialState, + reducers: { + setCurrentUser(state, action: PayloadAction) { + state.user = action.payload + }, + clearCurrentUser(state) { + state.user = null + }, + }, +}) + +export const { setCurrentUser, clearCurrentUser } = authSlice.actions +export const authReducer = authSlice.reducer diff --git a/src/store/index.ts b/src/store/index.ts index 8e33ca8..aa18f6b 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -11,6 +11,7 @@ import { REGISTER, } from "redux-persist" import createWebStorage from "redux-persist/es/storage/createWebStorage" +import { authReducer } from "./auth-slice" import { firearmsReducer } from "./firearms-slice" const storage = createWebStorage(import.meta.env.VITE_REDUX_STORAGE ?? "local") @@ -18,10 +19,11 @@ const storage = createWebStorage(import.meta.env.VITE_REDUX_STORAGE ?? "local") const persistConfig = { key: "root", storage, - whitelist: ["firearms"], + whitelist: ["auth", "firearms"], } const rootReducer = combineReducers({ + auth: authReducer, firearms: firearmsReducer }) diff --git a/src/types/auth.ts b/src/types/auth.ts new file mode 100644 index 0000000..34c2b14 --- /dev/null +++ b/src/types/auth.ts @@ -0,0 +1,11 @@ +export interface LoginRequest { + principle: string + credential: string +} + +export interface User { + id: number + username: string + email: string +} + diff --git a/src/types/common.ts b/src/types/common.ts new file mode 100644 index 0000000..9f98731 --- /dev/null +++ b/src/types/common.ts @@ -0,0 +1,17 @@ +export type Direction = "ASC" | "DESC" + +export interface Page { + items: T[] + page: number + size: number + totalElements: number + totalPages: number +} + +export interface PageQueryParams { + page?: number + size?: number + sortBy?: string + direction?: Direction +} + diff --git a/src/types/firearm.ts b/src/types/firearm.ts new file mode 100644 index 0000000..596f02d --- /dev/null +++ b/src/types/firearm.ts @@ -0,0 +1,24 @@ +export type FirearmType = + | "RIFLE" + | "SUB_MACHINE_GUN" + | "SHOTGUN" + | "LIGHT_MACHINE_GUN" + | "DESIGNATED_MARKSMAN_RIFLE" + | "SNIPER_RIFLE" + | "PISTOL" + | "SPECIAL" + +export interface Firearm { + id: number + name: string + type: FirearmType + level: string + calibre: string + fireRate: number + armourDamage: number + bodyDamage: number + review: string | null +} + +export interface AddFirearmRequest extends Omit {} + diff --git a/src/types/index.ts b/src/types/index.ts index 94c898b..785c148 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,49 +1,4 @@ -export type Direction = "ASC" | "DESC" - -export type FirearmType = - | "RIFLE" - | "SUB_MACHINE_GUN" - | "SHOTGUN" - | "LIGHT_MACHINE_GUN" - | "DESIGNATED_MARKSMAN_RIFLE" - | "SNIPER_RIFLE" - | "PISTOL" - | "SPECIAL" - -export interface Page { - items: T[] - page: number - size: number - totalElements: number - totalPages: number -} - -export interface Firearm { - id: number - name: string - type: FirearmType - level: string - calibre: string - fireRate: number - armourDamage: number - bodyDamage: number - review: string -} - -export interface Modification { - id: number - firearmId: number - name: string - code: string - tags: string[] - note: string - author: string - videoUrl: string -} - -export interface PageQueryParams { - page?: number - size?: number - sortBy?: string - direction?: Direction -} +export * from "./common" +export * from "./firearm" +export * from "./modification" +export * from "./auth" diff --git a/src/types/modification.ts b/src/types/modification.ts new file mode 100644 index 0000000..20b6e58 --- /dev/null +++ b/src/types/modification.ts @@ -0,0 +1,24 @@ +export interface Tuning { + tuningName: string + tuningValue: number +} + +export interface Accessory { + slotName: string + accessoryName: string + tunings: Tuning[] +} + +export interface Modification { + id: number + firearmId: number + name: string + code: string + tags?: string[] + note?: string + author?: string + videoUrl?: string, + accessories: Accessory[] +} + +export interface ModificationRequest extends Omit {} diff --git a/vite.config.ts b/vite.config.ts index 45b6aba..4eec181 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,4 +12,13 @@ export default defineConfig({ "@": fileURLToPath(new URL("./src", import.meta.url)), }, }, + server: { + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + } })