feat: prefix and anti multi-check

This commit is contained in:
KirinBaka
2025-04-22 15:34:22 +08:00
parent bebf2c9640
commit ffc89fd069
10 changed files with 46 additions and 28 deletions

View File

@@ -1,14 +1,17 @@
const API_PREFIX = "/duty";
export const get = async (url: string) => { export const get = async (url: string) => {
const resp = await fetch(url); const resp = await fetch(`${API_PREFIX}${url}`);
const json = await resp.json(); const json = await resp.json();
return json; return json;
}; };
export const post = async ( export const post = async (
url: string, url: string,
json: any, json: any,
headers: Record<string, string> = {} headers: Record<string, string> = {}
) => { ) => {
const resp = await fetch(url, { const resp = await fetch(`${API_PREFIX}${url}`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", ...headers }, headers: { "Content-Type": "application/json", ...headers },
body: JSON.stringify(json), body: JSON.stringify(json),

View File

@@ -79,10 +79,13 @@ const Timetable = ({
if (disableNetwork) return; if (disableNetwork) return;
// Immediately revert the checkbox state
target.checked = !target.checked;
// post request // post request
const json = await post("/api/record", { const json = await post("/api/record", {
name: target.name, name: target.name,
checked: target.checked, checked: !target.checked,
user, user,
}); });
if (json.error !== undefined) { if (json.error !== undefined) {
@@ -100,6 +103,7 @@ const Timetable = ({
const handleInput = (event: React.ChangeEvent<HTMLInputElement>): boolean => { const handleInput = (event: React.ChangeEvent<HTMLInputElement>): boolean => {
const { target } = event; const { target } = event;
// validate // validate
if (target?.children[0]?.tagName !== "TABLE") { if (target?.children[0]?.tagName !== "TABLE") {
console.log("not a table"); console.log("not a table");
@@ -202,7 +206,7 @@ const Timetable = ({
} }
const occupied: string[] = json.occupied; const occupied: string[] = json.occupied;
const myselect: string[] = json.myselect; const myselect: string[] = json.myselect;
console.log(json); // console.log(json);
for (const index in indexToElement) { for (const index in indexToElement) {
if (occupied.includes(index)) { if (occupied.includes(index)) {
indexToElement[index].style.display = "none"; indexToElement[index].style.display = "none";
@@ -226,7 +230,7 @@ const Timetable = ({
const interval = setInterval(() => { const interval = setInterval(() => {
refresh(); refresh();
}, 1000); }, 1500);
return () => { return () => {
clearInterval(interval); clearInterval(interval);
}; };
@@ -264,7 +268,9 @@ const Timetable = ({
return ( return (
<> <>
<h2 style={{ textAlign: "center" }}>Login as {user}</h2> <h2 style={{ textAlign: "center" }}>ITSC </h2>
<h3 style={{ textAlign: "center" }}>Login as {user}</h3>
<h3 style={{ textAlign: "center" }}></h3>
<div <div
align="center" align="center"
ref={ref} ref={ref}
@@ -273,7 +279,7 @@ const Timetable = ({
overflow: "scroll", overflow: "scroll",
}} }}
onInput={handleInput} onInput={handleInput}
></div>{" "} ></div>
{!hideDownloadButton && ( {!hideDownloadButton && (
<p style={{ display: "flex", justifyContent: "center" }}> <p style={{ display: "flex", justifyContent: "center" }}>
<button onClick={DownloadMarks}>Download Selection</button> <button onClick={DownloadMarks}>Download Selection</button>

View File

@@ -6,7 +6,7 @@ const UserInputWrap = ({ children, setUser }) => {
const [begin, setBegin] = React.useState(false); const [begin, setBegin] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
setInputUser(localStorage.getItem("user") || ""); setInputUser(localStorage.getItem("username") || "");
}, []); }, []);
return ( return (
@@ -17,9 +17,10 @@ const UserInputWrap = ({ children, setUser }) => {
display: "grid", display: "grid",
placeItems: "center", placeItems: "center",
alignItems: "center", alignItems: "center",
minHeight: "100vh", // minHeight: "100vh",
}} }}
> >
<h2 style={{ textAlign: "center" }}>ITSC </h2>
<div> <div>
<input <input
style={{ style={{
@@ -45,7 +46,7 @@ const UserInputWrap = ({ children, setUser }) => {
} }
setUser(inputUser.trim()); setUser(inputUser.trim());
setBegin(true); setBegin(true);
localStorage.setItem("user", inputUser.trim()); localStorage.setItem("username", inputUser.trim());
}} }}
> >
Login Login

BIN
data.zip Normal file

Binary file not shown.

View File

@@ -4,7 +4,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"esbuild": "./node_modules/.bin/esbuild --bundle --tsconfig=entrypoints/tsconfig.json --alias:'@'='./' --outdir=entrypoints --splitting --format=esm entrypoints/index.tsx entrypoints/edit/index.tsx entrypoints/report/index.tsx entrypoints/control/index.tsx --minify", "esbuild": "./node_modules/.bin/esbuild --bundle --tsconfig=entrypoints/tsconfig.json --alias:'@'='./' --outdir=entrypoints --splitting --format=esm entrypoints/index.tsx entrypoints/edit/index.tsx entrypoints/report/index.tsx entrypoints/control/index.tsx --minify",
"dev": "next dev", "dev": "next dev -p 4000",
"build": "next build", "build": "next build",
"start": "next start -p 4000", "start": "next start -p 4000",
"lint": "next lint" "lint": "next lint"

View File

@@ -22,25 +22,35 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
count += 1; count += 1;
if (count >= config.limit) { if (count >= config.limit) {
res.status(403).json({ res.status(403).json({
error: `超过选择数量限制,您至多选 ${config.limit} 个班.`, error: `超过选择数量限制,您至多选 ${config.limit} 个班`,
}); });
return; return;
} }
} }
} }
// check whether it is already occupied // check whether the user repeatly select
if (store[json.name] !== undefined) { if (store[json.name] === json.user) {
console.log("api::request: repeat select", json);
res.status(403).json({ res.status(403).json({
error: `当前位置已被他人占用`, error: `您已经选择了这个班次,请勿重复选择`,
});
return;
}
// check whether it is already occupied
else if (store[json.name] !== undefined) {
console.log("api::request: occupied", json);
res.status(403).json({
error: `当前位置已被他人占用,请选择其他班次`,
}); });
return; return;
} }
store[json.name] = json.user; store[json.name] = json.user;
} else { } else {
// console.log(store, json);
// check whether the request name match the taken name // check whether the request name match the taken name
if (store[json.name] !== json.user) { if (store[json.name] !== json.user) {
res.status(403).json({ res.status(403).json({
error: `失败:您未选择到当前位置`, error: `您已经取消了这个班次,请勿重复点击复选框`,
}) })
return; return;
} }

View File

@@ -10,8 +10,8 @@ export default function Home() {
<> <>
<Head> <Head>
<title> </title> <title> </title>
<meta name="description" content="ITSC抢班系统" /> <meta name="description" content="ITSC 学 生 助 理 抢 班 系 统" />
{/* <meta name="viewport" content="width=device-width, initial-scale=1" /> */} <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<main> <main>

View File

@@ -1,14 +1,13 @@
import React from "react"; import React from "react";
import Head from "next/head"; import Head from "next/head";
import { get, post } from "@/common";
const ReportPage = () => { const ReportPage = () => {
const ref = React.useRef(); const ref = React.useRef();
const getReport = async () => { const getReport = async () => {
const resp = await fetch("/api/html").then((resp) => resp.json()); const resp = await get("/api/html");
ref.current.innerHTML = resp.html; ref.current.innerHTML = resp.html;
const json: Record<string, string> = await fetch("/api/tool").then((resp) => const json: Record<string, string> = await get("/api/tool");
resp.json()
);
const table = ref.current.children[0]; const table = ref.current.children[0];
const tbody = table.children[table.children.length - 1]; const tbody = table.children[table.children.length - 1];
for (const tr_index in tbody.children) { for (const tr_index in tbody.children) {

View File

@@ -1,14 +1,13 @@
import React from "react"; import React from "react";
import Head from "next/head"; import Head from "next/head";
import { get, post } from "@/common";
const ReportPage = () => { const ReportPage = () => {
const ref = React.useRef(); const ref = React.useRef();
const getReport = async () => { const getReport = async () => {
const resp = await fetch("/api/html").then((resp) => resp.json()); const resp = await get("/api/html");
ref.current.innerHTML = resp.html; ref.current.innerHTML = resp.html;
const json: Record<string, string> = await fetch("/api/admin").then( const json: Record<string, string> = await get("/api/admin");
(resp) => resp.json()
);
const table = ref.current.children[0]; const table = ref.current.children[0];
const tbody = table.children[table.children.length - 1]; const tbody = table.children[table.children.length - 1];
for (const tr_index in tbody.children) { for (const tr_index in tbody.children) {

View File

@@ -7,7 +7,7 @@ export default function Home() {
const [begin, setBegin] = React.useState(false); const [begin, setBegin] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
setUser(localStorage.getItem("user") || ""); setUser(localStorage.getItem("username") || "");
}, []); }, []);
return ( return (
@@ -34,7 +34,7 @@ export default function Home() {
} }
setUser(user.trim()); setUser(user.trim());
setBegin(true); setBegin(true);
localStorage.setItem("user", user); localStorage.setItem("username", user);
}} }}
> >
Login Login