mirror of
https://github.com/m1ngsama/FUJI.git
synced 2025-12-24 10:51:27 +00:00
Merge branch 'dev'
This commit is contained in:
commit
b1ab6cd2a5
32 changed files with 1593 additions and 237 deletions
|
|
@ -225,4 +225,15 @@ export default defineConfig({
|
|||
experimentalReactChildren: true,
|
||||
}),
|
||||
],
|
||||
vite: {
|
||||
server: {
|
||||
proxy: {
|
||||
"/active": {
|
||||
target: "https://active.nbtca.space",
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace(/^\/active/, ""),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -54,6 +54,12 @@ export default [
|
|||
},
|
||||
},
|
||||
{
|
||||
ignores: ["dist/**/*", "public/**/*", "node_modules/**/*"],
|
||||
ignores: [
|
||||
"dist/**/*",
|
||||
"public/**/*",
|
||||
"node_modules/**/*",
|
||||
"src/utils/active/**/*",
|
||||
"openapi-ts.active.config.ts",
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -27,5 +27,14 @@ http {
|
|||
location / {
|
||||
try_files $uri $uri/index.html =404;
|
||||
}
|
||||
|
||||
location /active {
|
||||
proxy_pass https://active.nbtca.space;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
rewrite ^/active/(.*)$ /$1 break;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
openapi-ts.active.config.ts
Normal file
21
openapi-ts.active.config.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { defineConfig } from '@hey-api/openapi-ts'
|
||||
export default defineConfig({
|
||||
input: 'https://active.nbtca.space/openapi.json',
|
||||
output: {
|
||||
path: './src/utils/active',
|
||||
format: 'prettier',
|
||||
lint: 'eslint',
|
||||
},
|
||||
client: 'legacy/fetch',
|
||||
types: {
|
||||
dates: true,
|
||||
enums: 'typescript+namespace',
|
||||
name: 'PascalCase',
|
||||
tree: false,
|
||||
},
|
||||
services: {
|
||||
response: 'body',
|
||||
},
|
||||
schemas: false,
|
||||
name: 'ApiClient',
|
||||
})
|
||||
|
|
@ -10,7 +10,8 @@
|
|||
"lint": "eslint",
|
||||
"astro": "astro",
|
||||
"prepare": "husky",
|
||||
"gen-type": "openapi-typescript http://localhost:4000/openapi-3.0.json -o ./src/types/saturday.d.ts"
|
||||
"gen-type": "openapi-typescript http://localhost:4000/openapi-3.0.json -o ./src/types/saturday.d.ts",
|
||||
"active": "openapi-ts -f openapi-ts.active.config.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/react": "^3.3.1",
|
||||
|
|
@ -45,6 +46,7 @@
|
|||
"@astrojs/markdown-remark": "^5.2.0",
|
||||
"@cspell/eslint-plugin": "^8.8.1",
|
||||
"@eslint/js": "^9.11.0",
|
||||
"@hey-api/openapi-ts": "^0.53.3",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/md5": "^2.3.5",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
|
|
@ -65,4 +67,4 @@
|
|||
"lint-staged": {
|
||||
"*.{js,jsx,mjs,ts,tsx,mts,astro}": "eslint --fix"
|
||||
}
|
||||
}
|
||||
}
|
||||
662
pnpm-lock.yaml
662
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -27,17 +27,18 @@ export default function App() {
|
|||
return (
|
||||
<Navbar onMenuOpenChange={setIsMenuOpen} height="48px">
|
||||
<NavbarContent className="flex justify-between items-center">
|
||||
<NavbarBrand className="flex gap-4" onClick={() => window.location.href = "/"}>
|
||||
<NavbarBrand className="flex gap-4">
|
||||
<img
|
||||
src="https://oss.nbtca.space/CA-logo.svg"
|
||||
alt=""
|
||||
className="w-8 aspect-square"
|
||||
className="w-8 aspect-square cursor-pointer"
|
||||
onClick={() => window.location.href = "/"}
|
||||
/>
|
||||
<span className="text-lg text-[#1d1d1f]">
|
||||
<span className="hidden sm:flex select-none cursor-default text-lg text-[#1d1d1f]">
|
||||
{SITE_TITLE}
|
||||
</span>
|
||||
</NavbarBrand>
|
||||
<NavbarContent className="hidden sm:flex gap-4" justify="center">
|
||||
<NavbarContent className="hidden sm:flex gap-[24px]" justify="center">
|
||||
{
|
||||
menuItems.map(item => (
|
||||
<NavbarItem key={item.name}>
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
import FullCalendar from "../components/react/Calendar"
|
||||
import BaseLayout from "../layouts/BaseLayout.astro"
|
||||
---
|
||||
|
||||
<BaseLayout primaryTitle="日历">
|
||||
<FullCalendar client:load />
|
||||
</BaseLayout>
|
||||
8
src/pages/calendar/index.astro
Normal file
8
src/pages/calendar/index.astro
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
import FullCalendar from "./Calendar"
|
||||
import BaseLayout from "../../layouts/BaseLayout.astro"
|
||||
---
|
||||
|
||||
<BaseLayout primaryTitle="日历">
|
||||
<FullCalendar client:load />
|
||||
</BaseLayout>
|
||||
BIN
src/pages/freshman/_assets/nbtca.gif
Normal file
BIN
src/pages/freshman/_assets/nbtca.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
|
|
@ -1,11 +1,17 @@
|
|||
---
|
||||
import BaseLayout from "../../layouts/BaseLayout.astro"
|
||||
// import { Button } from "@nextui-org/react";
|
||||
import ReactChild from "./react/acontent"
|
||||
import ReactChild from "./react/join.tsx"
|
||||
import logo from "./_assets/nbtca.gif";
|
||||
---
|
||||
|
||||
<BaseLayout title="Welcome to Astro.">
|
||||
<BaseLayout title="加入我们">
|
||||
<main>
|
||||
<ReactChild client:visible />
|
||||
<img src={logo.src} />
|
||||
<ReactChild client:load />
|
||||
<div>
|
||||
1.本页面仅作计算机协会新人信息登记使用,原则上登记即可加入。但入社后可能仍需要在学校的平台登记,后续请留意群内通知。
|
||||
<br />
|
||||
2.允许多次提交表单,通常以最后一次提交为准。
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
|
|
|
|||
10
src/pages/freshman/list.astro
Normal file
10
src/pages/freshman/list.astro
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
import BaseLayout from "../../layouts/BaseLayout.astro"
|
||||
// import ReactChild from "./react/list.tsx";
|
||||
---
|
||||
|
||||
<BaseLayout title="列表">
|
||||
<main>
|
||||
<!-- <ReactChild client:load /> -->
|
||||
</main>
|
||||
</BaseLayout>
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// app/page.tsx
|
||||
import { Button } from "@nextui-org/react"
|
||||
|
||||
// import { NextUIProvider } from "@nextui-org/react";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<Button>Click me</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
124
src/pages/freshman/react/join.tsx
Normal file
124
src/pages/freshman/react/join.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { useState } from "react"
|
||||
import { Button, Input, Card, CardBody, CardFooter } from "@nextui-org/react"
|
||||
import { activeClient } from "../../../utils/client"
|
||||
|
||||
export default function JoinForm() {
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
class: "",
|
||||
number: "",
|
||||
major: "",
|
||||
phone: "",
|
||||
qq: "",
|
||||
email: "",
|
||||
memo: "",
|
||||
})
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target
|
||||
setFormData(prevData => ({
|
||||
...prevData,
|
||||
[name]: value,
|
||||
}))
|
||||
}
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await activeClient.freshman.postFreshmanAdd({
|
||||
requestBody: formData,
|
||||
})
|
||||
alert("提交成功! 后续请加群获取!")
|
||||
window.location.href = "/about"
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error submitting form:", error)
|
||||
alert("Failed to submit form.")
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<form>
|
||||
<Card>
|
||||
<CardBody
|
||||
style={{
|
||||
maxWidth: "700px",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
name="name"
|
||||
placeholder="姓名"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
name="class"
|
||||
placeholder="班级"
|
||||
value={formData.class}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
name="number"
|
||||
placeholder="学号"
|
||||
value={formData.number}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
name="major"
|
||||
placeholder="专业"
|
||||
value={formData.major}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
name="phone"
|
||||
placeholder="电话"
|
||||
value={formData.phone}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
name="qq"
|
||||
placeholder="QQ"
|
||||
value={formData.qq}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
name="email"
|
||||
placeholder="邮箱"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
name="memo"
|
||||
placeholder="备注"
|
||||
value={formData.memo}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</CardBody>
|
||||
<CardFooter
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<Button onClick={handleSubmit}>提交表单</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
61
src/pages/freshman/react/list.tsx
Normal file
61
src/pages/freshman/react/list.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import {
|
||||
// Button,
|
||||
// Input,
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
// PaginationItem,
|
||||
} from "@nextui-org/react"
|
||||
import {
|
||||
activeClient,
|
||||
type GetFreshmanListResponse,
|
||||
} from "../../../utils/client"
|
||||
// activeClient.freshman.getFreshmanList();
|
||||
import { useEffect, useState } from "react"
|
||||
// import { Pagination } from "@nextui-org/react";
|
||||
const FreshmanList = () => {
|
||||
const [freshmen, setFreshmen] = useState<GetFreshmanListResponse>({
|
||||
list: [],
|
||||
total: 0,
|
||||
})
|
||||
const [currentPage] = useState(1) // setCurrentPage
|
||||
useEffect(() => {
|
||||
const fetchFreshmen = async () => {
|
||||
const result = await activeClient.freshman.getFreshmanList({
|
||||
page: currentPage,
|
||||
})
|
||||
setFreshmen(result)
|
||||
}
|
||||
fetchFreshmen()
|
||||
}, [])
|
||||
return (
|
||||
<div>
|
||||
<Card>
|
||||
<CardBody>
|
||||
{freshmen.list.map(freshman => (
|
||||
<div key={freshman.number}>
|
||||
<div>{freshman.name}</div>
|
||||
<div>{freshman.number}</div>
|
||||
<div>{freshman.major}</div>
|
||||
<div>{freshman.class}</div>
|
||||
<div>{freshman.email}</div>
|
||||
<div>{freshman.phone}</div>
|
||||
<div>{freshman.qq}</div>
|
||||
</div>
|
||||
))}
|
||||
</CardBody>
|
||||
<CardFooter>
|
||||
{/* <PaginationItem
|
||||
// count={Math.ceil(freshmen.total / ITEMS_PER_PAGE)}
|
||||
page={currentPage}
|
||||
onChange={(page) => {
|
||||
setCurrentPage(page);
|
||||
}}
|
||||
/> */}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FreshmanList
|
||||
|
|
@ -4,10 +4,12 @@ import Tile from "../layouts/Tile.astro"
|
|||
import MoreTile from "../layouts/MoreTile.astro"
|
||||
const allPosts = await Astro.glob("../pages/posts/*.md")
|
||||
const Blogs = await Astro.glob("../pages/posts/blogs/**/*.md")
|
||||
allPosts.sort(
|
||||
(a, b) =>
|
||||
Date.parse(b.frontmatter.pubDate) - Date.parse(a.frontmatter.pubDate),
|
||||
)
|
||||
allPosts.sort((a, b) => {
|
||||
if ((a.frontmatter.title as string).startsWith("计算机协会入社指南")) {
|
||||
return -99999999999
|
||||
}
|
||||
return Date.parse(b.frontmatter.pubDate) - Date.parse(a.frontmatter.pubDate)
|
||||
})
|
||||
|
||||
Blogs.sort(
|
||||
(a, b) =>
|
||||
|
|
|
|||
|
|
@ -18,14 +18,24 @@ import mp4 from "./_assets/nbtca.mp4";
|
|||
<p class="life-at-apple__body-text">九月二十五日,百团等你!</p>
|
||||
</div>
|
||||
<div class="learn-more-link-container">
|
||||
<a
|
||||
href="/posts/计算机协会入社指南"
|
||||
class="learn-more-link"
|
||||
aria-label="进一步了解 NBTCA 的社区和文化"
|
||||
>
|
||||
了解在 NBTCA
|
||||
</a>
|
||||
<a class="learn-more-link-after">> </a>
|
||||
<div class="learn-more-link">
|
||||
<a
|
||||
href="/posts/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%8D%8F%E4%BC%9A%E5%85%A5%E7%A4%BE%E6%8C%87%E5%8D%97"
|
||||
aria-label="进一步了解 NBTCA 的社区和文化"
|
||||
>
|
||||
了解在 NBTCA
|
||||
</a>
|
||||
<a class="learn-more-link-after">> </a>
|
||||
<br />
|
||||
<br />
|
||||
<a
|
||||
href="/freshman/join"
|
||||
aria-label="申请加入 NBTCA"
|
||||
>
|
||||
填写入社信息
|
||||
</a>
|
||||
<a class="learn-more-link-after">> </a>
|
||||
</div>
|
||||
</div>
|
||||
<video width="640" height="360" autoplay muted>
|
||||
<source src={mp4} type="video/mp4" />
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ tags: ["指南"]
|
|||
|
||||
# 看不见的地方 🗿
|
||||
|
||||
- 上面的很多工作是由一群维护[服务器](https://i.nbtca.space/)和代码的同志提供服务支持的,所以我们在看不见的地方有一些工作,如果你希望参与进来,这里有一份[参考 💾](此条目等待扩充),当然你也可以[加入我们](此条目等待扩充)同我们一起研究。
|
||||
- 上面的很多工作是由一群维护[服务器](https://i.nbtca.space/)和代码的同志提供服务支持的,所以我们在看不见的地方有一些工作,如果你希望参与进来,这里有一份[参考 💾](https://github.com/nbtca/documents/blob/main/%E5%BC%80%E5%8F%91%E7%BB%84/2023.10.%E5%BC%80%E5%8F%91%E7%BB%84%E6%96%B0%E4%BA%BA%E5%9F%B9%E5%85%BB%E6%96%B9%E6%A1%88.md),当然你也可以[加入我们](/join-us)同我们一起研究。
|
||||
- mc 服务器建设[🎮](https://mc.nbtca.space/)
|
||||
- 电子游戏竞赛?(你唠这个我可就不困了 🤠)
|
||||
- EDC、桌搭、摄影、户外?进来唠
|
||||
|
|
|
|||
37
src/utils/active/ApiClient.ts
Normal file
37
src/utils/active/ApiClient.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import type { BaseHttpRequest } from "./core/BaseHttpRequest";
|
||||
import type { OpenAPIConfig } from "./core/OpenAPI";
|
||||
import { Interceptors } from "./core/OpenAPI";
|
||||
import { FetchHttpRequest } from "./core/FetchHttpRequest";
|
||||
|
||||
import { FreshmanService } from "./services.gen";
|
||||
|
||||
type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest;
|
||||
|
||||
export class ApiClient {
|
||||
public readonly freshman: FreshmanService;
|
||||
|
||||
public readonly request: BaseHttpRequest;
|
||||
|
||||
constructor(
|
||||
config?: Partial<OpenAPIConfig>,
|
||||
HttpRequest: HttpRequestConstructor = FetchHttpRequest,
|
||||
) {
|
||||
this.request = new HttpRequest({
|
||||
BASE: config?.BASE ?? "",
|
||||
VERSION: config?.VERSION ?? "1.0.0",
|
||||
WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
|
||||
CREDENTIALS: config?.CREDENTIALS ?? "include",
|
||||
TOKEN: config?.TOKEN,
|
||||
USERNAME: config?.USERNAME,
|
||||
PASSWORD: config?.PASSWORD,
|
||||
HEADERS: config?.HEADERS,
|
||||
ENCODE_PATH: config?.ENCODE_PATH,
|
||||
interceptors: {
|
||||
request: config?.interceptors?.request ?? new Interceptors(),
|
||||
response: config?.interceptors?.response ?? new Interceptors(),
|
||||
},
|
||||
});
|
||||
|
||||
this.freshman = new FreshmanService(this.request);
|
||||
}
|
||||
}
|
||||
25
src/utils/active/core/ApiError.ts
Normal file
25
src/utils/active/core/ApiError.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import type { ApiRequestOptions } from "./ApiRequestOptions";
|
||||
import type { ApiResult } from "./ApiResult";
|
||||
|
||||
export class ApiError extends Error {
|
||||
public readonly url: string;
|
||||
public readonly status: number;
|
||||
public readonly statusText: string;
|
||||
public readonly body: unknown;
|
||||
public readonly request: ApiRequestOptions;
|
||||
|
||||
constructor(
|
||||
request: ApiRequestOptions,
|
||||
response: ApiResult,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
|
||||
this.name = "ApiError";
|
||||
this.url = response.url;
|
||||
this.status = response.status;
|
||||
this.statusText = response.statusText;
|
||||
this.body = response.body;
|
||||
this.request = request;
|
||||
}
|
||||
}
|
||||
21
src/utils/active/core/ApiRequestOptions.ts
Normal file
21
src/utils/active/core/ApiRequestOptions.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
export type ApiRequestOptions<T = unknown> = {
|
||||
readonly body?: any;
|
||||
readonly cookies?: Record<string, unknown>;
|
||||
readonly errors?: Record<number | string, string>;
|
||||
readonly formData?: Record<string, unknown> | any[] | Blob | File;
|
||||
readonly headers?: Record<string, unknown>;
|
||||
readonly mediaType?: string;
|
||||
readonly method:
|
||||
| "DELETE"
|
||||
| "GET"
|
||||
| "HEAD"
|
||||
| "OPTIONS"
|
||||
| "PATCH"
|
||||
| "POST"
|
||||
| "PUT";
|
||||
readonly path?: Record<string, unknown>;
|
||||
readonly query?: Record<string, unknown>;
|
||||
readonly responseHeader?: string;
|
||||
readonly responseTransformer?: (data: unknown) => Promise<T>;
|
||||
readonly url: string;
|
||||
};
|
||||
7
src/utils/active/core/ApiResult.ts
Normal file
7
src/utils/active/core/ApiResult.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export type ApiResult<TData = any> = {
|
||||
readonly body: TData;
|
||||
readonly ok: boolean;
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
readonly url: string;
|
||||
};
|
||||
11
src/utils/active/core/BaseHttpRequest.ts
Normal file
11
src/utils/active/core/BaseHttpRequest.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import type { ApiRequestOptions } from "./ApiRequestOptions";
|
||||
import type { CancelablePromise } from "./CancelablePromise";
|
||||
import type { OpenAPIConfig } from "./OpenAPI";
|
||||
|
||||
export abstract class BaseHttpRequest {
|
||||
constructor(public readonly config: OpenAPIConfig) {}
|
||||
|
||||
public abstract request<T>(
|
||||
options: ApiRequestOptions<T>,
|
||||
): CancelablePromise<T>;
|
||||
}
|
||||
126
src/utils/active/core/CancelablePromise.ts
Normal file
126
src/utils/active/core/CancelablePromise.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
export class CancelError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "CancelError";
|
||||
}
|
||||
|
||||
public get isCancelled(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface OnCancel {
|
||||
readonly isResolved: boolean;
|
||||
readonly isRejected: boolean;
|
||||
readonly isCancelled: boolean;
|
||||
|
||||
(cancelHandler: () => void): void;
|
||||
}
|
||||
|
||||
export class CancelablePromise<T> implements Promise<T> {
|
||||
private _isResolved: boolean;
|
||||
private _isRejected: boolean;
|
||||
private _isCancelled: boolean;
|
||||
readonly cancelHandlers: (() => void)[];
|
||||
readonly promise: Promise<T>;
|
||||
private _resolve?: (value: T | PromiseLike<T>) => void;
|
||||
private _reject?: (reason?: unknown) => void;
|
||||
|
||||
constructor(
|
||||
executor: (
|
||||
resolve: (value: T | PromiseLike<T>) => void,
|
||||
reject: (reason?: unknown) => void,
|
||||
onCancel: OnCancel,
|
||||
) => void,
|
||||
) {
|
||||
this._isResolved = false;
|
||||
this._isRejected = false;
|
||||
this._isCancelled = false;
|
||||
this.cancelHandlers = [];
|
||||
this.promise = new Promise<T>((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
|
||||
const onResolve = (value: T | PromiseLike<T>): void => {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return;
|
||||
}
|
||||
this._isResolved = true;
|
||||
if (this._resolve) this._resolve(value);
|
||||
};
|
||||
|
||||
const onReject = (reason?: unknown): void => {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return;
|
||||
}
|
||||
this._isRejected = true;
|
||||
if (this._reject) this._reject(reason);
|
||||
};
|
||||
|
||||
const onCancel = (cancelHandler: () => void): void => {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return;
|
||||
}
|
||||
this.cancelHandlers.push(cancelHandler);
|
||||
};
|
||||
|
||||
Object.defineProperty(onCancel, "isResolved", {
|
||||
get: (): boolean => this._isResolved,
|
||||
});
|
||||
|
||||
Object.defineProperty(onCancel, "isRejected", {
|
||||
get: (): boolean => this._isRejected,
|
||||
});
|
||||
|
||||
Object.defineProperty(onCancel, "isCancelled", {
|
||||
get: (): boolean => this._isCancelled,
|
||||
});
|
||||
|
||||
return executor(onResolve, onReject, onCancel as OnCancel);
|
||||
});
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return "Cancellable Promise";
|
||||
}
|
||||
|
||||
public then<TResult1 = T, TResult2 = never>(
|
||||
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
||||
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
|
||||
): Promise<TResult1 | TResult2> {
|
||||
return this.promise.then(onFulfilled, onRejected);
|
||||
}
|
||||
|
||||
public catch<TResult = never>(
|
||||
onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null,
|
||||
): Promise<T | TResult> {
|
||||
return this.promise.catch(onRejected);
|
||||
}
|
||||
|
||||
public finally(onFinally?: (() => void) | null): Promise<T> {
|
||||
return this.promise.finally(onFinally);
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return;
|
||||
}
|
||||
this._isCancelled = true;
|
||||
if (this.cancelHandlers.length) {
|
||||
try {
|
||||
for (const cancelHandler of this.cancelHandlers) {
|
||||
cancelHandler();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Cancellation threw an error", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.cancelHandlers.length = 0;
|
||||
if (this._reject) this._reject(new CancelError("Request aborted"));
|
||||
}
|
||||
|
||||
public get isCancelled(): boolean {
|
||||
return this._isCancelled;
|
||||
}
|
||||
}
|
||||
23
src/utils/active/core/FetchHttpRequest.ts
Normal file
23
src/utils/active/core/FetchHttpRequest.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import type { ApiRequestOptions } from "./ApiRequestOptions";
|
||||
import { BaseHttpRequest } from "./BaseHttpRequest";
|
||||
import type { CancelablePromise } from "./CancelablePromise";
|
||||
import type { OpenAPIConfig } from "./OpenAPI";
|
||||
import { request as __request } from "./request";
|
||||
|
||||
export class FetchHttpRequest extends BaseHttpRequest {
|
||||
constructor(config: OpenAPIConfig) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request method
|
||||
* @param options The request options from the service
|
||||
* @returns CancelablePromise<T>
|
||||
* @throws ApiError
|
||||
*/
|
||||
public override request<T>(
|
||||
options: ApiRequestOptions<T>,
|
||||
): CancelablePromise<T> {
|
||||
return __request(this.config, options);
|
||||
}
|
||||
}
|
||||
56
src/utils/active/core/OpenAPI.ts
Normal file
56
src/utils/active/core/OpenAPI.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import type { ApiRequestOptions } from "./ApiRequestOptions";
|
||||
|
||||
type Headers = Record<string, string>;
|
||||
type Middleware<T> = (value: T) => T | Promise<T>;
|
||||
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
|
||||
|
||||
export class Interceptors<T> {
|
||||
_fns: Middleware<T>[];
|
||||
|
||||
constructor() {
|
||||
this._fns = [];
|
||||
}
|
||||
|
||||
eject(fn: Middleware<T>): void {
|
||||
const index = this._fns.indexOf(fn);
|
||||
if (index !== -1) {
|
||||
this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)];
|
||||
}
|
||||
}
|
||||
|
||||
use(fn: Middleware<T>): void {
|
||||
this._fns = [...this._fns, fn];
|
||||
}
|
||||
}
|
||||
|
||||
export type OpenAPIConfig = {
|
||||
BASE: string;
|
||||
CREDENTIALS: "include" | "omit" | "same-origin";
|
||||
ENCODE_PATH?: ((path: string) => string) | undefined;
|
||||
HEADERS?: Headers | Resolver<Headers> | undefined;
|
||||
PASSWORD?: string | Resolver<string> | undefined;
|
||||
TOKEN?: string | Resolver<string> | undefined;
|
||||
USERNAME?: string | Resolver<string> | undefined;
|
||||
VERSION: string;
|
||||
WITH_CREDENTIALS: boolean;
|
||||
interceptors: {
|
||||
request: Interceptors<RequestInit>;
|
||||
response: Interceptors<Response>;
|
||||
};
|
||||
};
|
||||
|
||||
export const OpenAPI: OpenAPIConfig = {
|
||||
BASE: "",
|
||||
CREDENTIALS: "include",
|
||||
ENCODE_PATH: undefined,
|
||||
HEADERS: undefined,
|
||||
PASSWORD: undefined,
|
||||
TOKEN: undefined,
|
||||
USERNAME: undefined,
|
||||
VERSION: "1.0.0",
|
||||
WITH_CREDENTIALS: false,
|
||||
interceptors: {
|
||||
request: new Interceptors(),
|
||||
response: new Interceptors(),
|
||||
},
|
||||
};
|
||||
400
src/utils/active/core/request.ts
Normal file
400
src/utils/active/core/request.ts
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
import { ApiError } from "./ApiError";
|
||||
import type { ApiRequestOptions } from "./ApiRequestOptions";
|
||||
import type { ApiResult } from "./ApiResult";
|
||||
import { CancelablePromise } from "./CancelablePromise";
|
||||
import type { OnCancel } from "./CancelablePromise";
|
||||
import type { OpenAPIConfig } from "./OpenAPI";
|
||||
|
||||
export const isString = (value: unknown): value is string => {
|
||||
return typeof value === "string";
|
||||
};
|
||||
|
||||
export const isStringWithValue = (value: unknown): value is string => {
|
||||
return isString(value) && value !== "";
|
||||
};
|
||||
|
||||
export const isBlob = (value: any): value is Blob => {
|
||||
return value instanceof Blob;
|
||||
};
|
||||
|
||||
export const isFormData = (value: unknown): value is FormData => {
|
||||
return value instanceof FormData;
|
||||
};
|
||||
|
||||
export const base64 = (str: string): string => {
|
||||
try {
|
||||
return btoa(str);
|
||||
} catch (err) {
|
||||
// @ts-ignore
|
||||
return Buffer.from(str).toString("base64");
|
||||
}
|
||||
};
|
||||
|
||||
export const getQueryString = (params: Record<string, unknown>): string => {
|
||||
const qs: string[] = [];
|
||||
|
||||
const append = (key: string, value: unknown) => {
|
||||
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
||||
};
|
||||
|
||||
const encodePair = (key: string, value: unknown) => {
|
||||
if (value === undefined || value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
append(key, value.toISOString());
|
||||
} else if (Array.isArray(value)) {
|
||||
value.forEach((v) => encodePair(key, v));
|
||||
} else if (typeof value === "object") {
|
||||
Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v));
|
||||
} else {
|
||||
append(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
Object.entries(params).forEach(([key, value]) => encodePair(key, value));
|
||||
|
||||
return qs.length ? `?${qs.join("&")}` : "";
|
||||
};
|
||||
|
||||
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
|
||||
const encoder = config.ENCODE_PATH || encodeURI;
|
||||
|
||||
const path = options.url
|
||||
.replace("{api-version}", config.VERSION)
|
||||
.replace(/{(.*?)}/g, (substring: string, group: string) => {
|
||||
if (options.path?.hasOwnProperty(group)) {
|
||||
return encoder(String(options.path[group]));
|
||||
}
|
||||
return substring;
|
||||
});
|
||||
|
||||
const url = config.BASE + path;
|
||||
return options.query ? url + getQueryString(options.query) : url;
|
||||
};
|
||||
|
||||
export const getFormData = (
|
||||
options: ApiRequestOptions,
|
||||
): FormData | undefined => {
|
||||
if (options.formData) {
|
||||
const formData = new FormData();
|
||||
|
||||
const process = (key: string, value: unknown) => {
|
||||
if (isString(value) || isBlob(value)) {
|
||||
formData.append(key, value);
|
||||
} else {
|
||||
formData.append(key, JSON.stringify(value));
|
||||
}
|
||||
};
|
||||
|
||||
Object.entries(options.formData)
|
||||
.filter(([, value]) => value !== undefined && value !== null)
|
||||
.forEach(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((v) => process(key, v));
|
||||
} else {
|
||||
process(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
return formData;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
|
||||
|
||||
export const resolve = async <T>(
|
||||
options: ApiRequestOptions<T>,
|
||||
resolver?: T | Resolver<T>,
|
||||
): Promise<T | undefined> => {
|
||||
if (typeof resolver === "function") {
|
||||
return (resolver as Resolver<T>)(options);
|
||||
}
|
||||
return resolver;
|
||||
};
|
||||
|
||||
export const getHeaders = async <T>(
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions<T>,
|
||||
): Promise<Headers> => {
|
||||
const [token, username, password, additionalHeaders] = await Promise.all([
|
||||
// @ts-ignore
|
||||
resolve(options, config.TOKEN),
|
||||
// @ts-ignore
|
||||
resolve(options, config.USERNAME),
|
||||
// @ts-ignore
|
||||
resolve(options, config.PASSWORD),
|
||||
// @ts-ignore
|
||||
resolve(options, config.HEADERS),
|
||||
]);
|
||||
|
||||
const headers = Object.entries({
|
||||
Accept: "application/json",
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
})
|
||||
.filter(([, value]) => value !== undefined && value !== null)
|
||||
.reduce(
|
||||
(headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: String(value),
|
||||
}),
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = base64(`${username}:${password}`);
|
||||
headers["Authorization"] = `Basic ${credentials}`;
|
||||
}
|
||||
|
||||
if (options.body !== undefined) {
|
||||
if (options.mediaType) {
|
||||
headers["Content-Type"] = options.mediaType;
|
||||
} else if (isBlob(options.body)) {
|
||||
headers["Content-Type"] = options.body.type || "application/octet-stream";
|
||||
} else if (isString(options.body)) {
|
||||
headers["Content-Type"] = "text/plain";
|
||||
} else if (!isFormData(options.body)) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
}
|
||||
|
||||
return new Headers(headers);
|
||||
};
|
||||
|
||||
export const getRequestBody = (options: ApiRequestOptions): unknown => {
|
||||
if (options.body !== undefined) {
|
||||
if (
|
||||
options.mediaType?.includes("application/json") ||
|
||||
options.mediaType?.includes("+json")
|
||||
) {
|
||||
return JSON.stringify(options.body);
|
||||
} else if (
|
||||
isString(options.body) ||
|
||||
isBlob(options.body) ||
|
||||
isFormData(options.body)
|
||||
) {
|
||||
return options.body;
|
||||
} else {
|
||||
return JSON.stringify(options.body);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const sendRequest = async (
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions,
|
||||
url: string,
|
||||
body: any,
|
||||
formData: FormData | undefined,
|
||||
headers: Headers,
|
||||
onCancel: OnCancel,
|
||||
): Promise<Response> => {
|
||||
const controller = new AbortController();
|
||||
|
||||
let request: RequestInit = {
|
||||
headers,
|
||||
body: body ?? formData,
|
||||
method: options.method,
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
if (config.WITH_CREDENTIALS) {
|
||||
request.credentials = config.CREDENTIALS;
|
||||
}
|
||||
|
||||
for (const fn of config.interceptors.request._fns) {
|
||||
request = await fn(request);
|
||||
}
|
||||
|
||||
onCancel(() => controller.abort());
|
||||
|
||||
return await fetch(url, request);
|
||||
};
|
||||
|
||||
export const getResponseHeader = (
|
||||
response: Response,
|
||||
responseHeader?: string,
|
||||
): string | undefined => {
|
||||
if (responseHeader) {
|
||||
const content = response.headers.get(responseHeader);
|
||||
if (isString(content)) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getResponseBody = async (response: Response): Promise<unknown> => {
|
||||
if (response.status !== 204) {
|
||||
try {
|
||||
const contentType = response.headers.get("Content-Type");
|
||||
if (contentType) {
|
||||
const binaryTypes = [
|
||||
"application/octet-stream",
|
||||
"application/pdf",
|
||||
"application/zip",
|
||||
"audio/",
|
||||
"image/",
|
||||
"video/",
|
||||
];
|
||||
if (
|
||||
contentType.includes("application/json") ||
|
||||
contentType.includes("+json")
|
||||
) {
|
||||
return await response.json();
|
||||
} else if (binaryTypes.some((type) => contentType.includes(type))) {
|
||||
return await response.blob();
|
||||
} else if (contentType.includes("multipart/form-data")) {
|
||||
return await response.formData();
|
||||
} else if (contentType.includes("text/")) {
|
||||
return await response.text();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const catchErrorCodes = (
|
||||
options: ApiRequestOptions,
|
||||
result: ApiResult,
|
||||
): void => {
|
||||
const errors: Record<number, string> = {
|
||||
400: "Bad Request",
|
||||
401: "Unauthorized",
|
||||
402: "Payment Required",
|
||||
403: "Forbidden",
|
||||
404: "Not Found",
|
||||
405: "Method Not Allowed",
|
||||
406: "Not Acceptable",
|
||||
407: "Proxy Authentication Required",
|
||||
408: "Request Timeout",
|
||||
409: "Conflict",
|
||||
410: "Gone",
|
||||
411: "Length Required",
|
||||
412: "Precondition Failed",
|
||||
413: "Payload Too Large",
|
||||
414: "URI Too Long",
|
||||
415: "Unsupported Media Type",
|
||||
416: "Range Not Satisfiable",
|
||||
417: "Expectation Failed",
|
||||
418: "Im a teapot",
|
||||
421: "Misdirected Request",
|
||||
422: "Unprocessable Content",
|
||||
423: "Locked",
|
||||
424: "Failed Dependency",
|
||||
425: "Too Early",
|
||||
426: "Upgrade Required",
|
||||
428: "Precondition Required",
|
||||
429: "Too Many Requests",
|
||||
431: "Request Header Fields Too Large",
|
||||
451: "Unavailable For Legal Reasons",
|
||||
500: "Internal Server Error",
|
||||
501: "Not Implemented",
|
||||
502: "Bad Gateway",
|
||||
503: "Service Unavailable",
|
||||
504: "Gateway Timeout",
|
||||
505: "HTTP Version Not Supported",
|
||||
506: "Variant Also Negotiates",
|
||||
507: "Insufficient Storage",
|
||||
508: "Loop Detected",
|
||||
510: "Not Extended",
|
||||
511: "Network Authentication Required",
|
||||
...options.errors,
|
||||
};
|
||||
|
||||
const error = errors[result.status];
|
||||
if (error) {
|
||||
throw new ApiError(options, result, error);
|
||||
}
|
||||
|
||||
if (!result.ok) {
|
||||
const errorStatus = result.status ?? "unknown";
|
||||
const errorStatusText = result.statusText ?? "unknown";
|
||||
const errorBody = (() => {
|
||||
try {
|
||||
return JSON.stringify(result.body, null, 2);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
|
||||
throw new ApiError(
|
||||
options,
|
||||
result,
|
||||
`Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Request method
|
||||
* @param config The OpenAPI configuration object
|
||||
* @param options The request options from the service
|
||||
* @returns CancelablePromise<T>
|
||||
* @throws ApiError
|
||||
*/
|
||||
export const request = <T>(
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions<T>,
|
||||
): CancelablePromise<T> => {
|
||||
return new CancelablePromise(async (resolve, reject, onCancel) => {
|
||||
try {
|
||||
const url = getUrl(config, options);
|
||||
const formData = getFormData(options);
|
||||
const body = getRequestBody(options);
|
||||
const headers = await getHeaders(config, options);
|
||||
|
||||
if (!onCancel.isCancelled) {
|
||||
let response = await sendRequest(
|
||||
config,
|
||||
options,
|
||||
url,
|
||||
body,
|
||||
formData,
|
||||
headers,
|
||||
onCancel,
|
||||
);
|
||||
|
||||
for (const fn of config.interceptors.response._fns) {
|
||||
response = await fn(response);
|
||||
}
|
||||
|
||||
const responseBody = await getResponseBody(response);
|
||||
const responseHeader = getResponseHeader(
|
||||
response,
|
||||
options.responseHeader,
|
||||
);
|
||||
|
||||
let transformedBody = responseBody;
|
||||
if (options.responseTransformer && response.ok) {
|
||||
transformedBody = await options.responseTransformer(responseBody);
|
||||
}
|
||||
|
||||
const result: ApiResult = {
|
||||
url,
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: responseHeader ?? transformedBody,
|
||||
};
|
||||
|
||||
catchErrorCodes(options, result);
|
||||
|
||||
resolve(result.body);
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
8
src/utils/active/index.ts
Normal file
8
src/utils/active/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
export { ApiClient } from "./ApiClient";
|
||||
export { ApiError } from "./core/ApiError";
|
||||
export { BaseHttpRequest } from "./core/BaseHttpRequest";
|
||||
export { CancelablePromise, CancelError } from "./core/CancelablePromise";
|
||||
export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI";
|
||||
export * from "./services.gen";
|
||||
export * from "./types.gen";
|
||||
54
src/utils/active/services.gen.ts
Normal file
54
src/utils/active/services.gen.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type { CancelablePromise } from "./core/CancelablePromise";
|
||||
import type { BaseHttpRequest } from "./core/BaseHttpRequest";
|
||||
import type {
|
||||
PostFreshmanAddData,
|
||||
PostFreshmanAddResponse,
|
||||
GetFreshmanListData,
|
||||
GetFreshmanListResponse,
|
||||
} from "./types.gen";
|
||||
|
||||
export class FreshmanService {
|
||||
constructor(public readonly httpRequest: BaseHttpRequest) {}
|
||||
|
||||
/**
|
||||
* 添加新人
|
||||
* @param data The data for the request.
|
||||
* @param data.requestBody
|
||||
* @returns unknown 返回创建结果
|
||||
* @throws ApiError
|
||||
*/
|
||||
public postFreshmanAdd(
|
||||
data: PostFreshmanAddData = {},
|
||||
): CancelablePromise<PostFreshmanAddResponse> {
|
||||
return this.httpRequest.request({
|
||||
method: "POST",
|
||||
url: "/api/freshman",
|
||||
body: data.requestBody,
|
||||
mediaType: "application/json",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新人列表
|
||||
* @param data The data for the request.
|
||||
* @param data.page 页码
|
||||
* @returns unknown 返回列表
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getFreshmanList(
|
||||
data: GetFreshmanListData = {},
|
||||
): CancelablePromise<GetFreshmanListResponse> {
|
||||
return this.httpRequest.request({
|
||||
method: "GET",
|
||||
url: "/api/freshman",
|
||||
query: {
|
||||
page: data.page,
|
||||
},
|
||||
errors: {
|
||||
500: "服务器错误",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
47
src/utils/active/types.gen.ts
Normal file
47
src/utils/active/types.gen.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
export type PostFreshmanAddData = {
|
||||
requestBody?: {
|
||||
name: string;
|
||||
number: string;
|
||||
major: string;
|
||||
class: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
qq: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type PostFreshmanAddResponse = {
|
||||
success: boolean;
|
||||
result?: {
|
||||
name: string;
|
||||
number: string;
|
||||
major: string;
|
||||
class: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
qq: string;
|
||||
};
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export type GetFreshmanListData = {
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
page?: number;
|
||||
};
|
||||
|
||||
export type GetFreshmanListResponse = {
|
||||
list: Array<{
|
||||
name: string;
|
||||
number: string;
|
||||
major: string;
|
||||
class: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
qq: string;
|
||||
}>;
|
||||
total: number;
|
||||
};
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
import createClient from "openapi-fetch"
|
||||
import type { paths } from "../types/saturday"
|
||||
|
||||
export const saturdayClient = createClient<paths>({
|
||||
import type { paths as saturdayPaths } from "../types/saturday"
|
||||
// import type { paths as activePaths } from "../types/active"
|
||||
import { ApiClient } from "./active"
|
||||
export const saturdayClient = createClient<saturdayPaths>({
|
||||
baseUrl: "https://api.nbtca.space/v2/",
|
||||
})
|
||||
// export const activeClient = createClient<activePaths>({
|
||||
// baseUrl: "https://active.nbtca.space/",
|
||||
// })
|
||||
export const activeClient = new ApiClient({
|
||||
BASE: "/active",
|
||||
})
|
||||
export * from "./active/types.gen"
|
||||
|
|
|
|||
Loading…
Reference in a new issue