Compare commits
12 Commits
3b01c2ec76
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3db0a652ce | ||
|
|
1049c8c8df | ||
|
|
fd3dd85963 | ||
| e8ce8492cf | |||
|
|
ffc89fd069 | ||
|
|
bebf2c9640 | ||
|
a04f1c22c9
|
|||
|
89124b9ad9
|
|||
|
6d2c0d8cb3
|
|||
|
7f2141e832
|
|||
|
55f48de4db
|
|||
|
7738e061fc
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,8 +1,10 @@
|
|||||||
|
/data
|
||||||
/html.html
|
/html.html
|
||||||
/json
|
/json
|
||||||
/hours.json
|
/hours.json
|
||||||
/regular.json
|
/regular.json
|
||||||
/record.json
|
/record.json
|
||||||
|
/store.json
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
|
const API_PREFIX = "";
|
||||||
|
|
||||||
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),
|
||||||
|
|||||||
@@ -35,11 +35,13 @@ const downloadObjectAsJson = (exportObj: any, exportName: string) => {
|
|||||||
|
|
||||||
const Timetable = ({
|
const Timetable = ({
|
||||||
user,
|
user,
|
||||||
|
isRegular = false,
|
||||||
disableNetwork = false,
|
disableNetwork = false,
|
||||||
disableConflictCheck = false,
|
disableConflictCheck = false,
|
||||||
replaceInputType = "checkbox",
|
replaceInputType = "checkbox",
|
||||||
apiRecordEndPoint = "/api/record",
|
apiRecordEndPoint = "/api/record",
|
||||||
openRecordMode = false,
|
openRecordMode = false,
|
||||||
|
hideDownloadButton = true,
|
||||||
}) => {
|
}) => {
|
||||||
const [editable, setEditable] = React.useState(true);
|
const [editable, setEditable] = React.useState(true);
|
||||||
const ref = React.useRef();
|
const ref = React.useRef();
|
||||||
@@ -77,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) {
|
||||||
@@ -98,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");
|
||||||
@@ -113,7 +119,7 @@ const Timetable = ({
|
|||||||
tds.length = 0;
|
tds.length = 0;
|
||||||
|
|
||||||
const table = target.children[0];
|
const table = target.children[0];
|
||||||
table.setAttribute("border", "1");
|
table.setAttribute("border", "0");
|
||||||
|
|
||||||
// mark cell
|
// mark cell
|
||||||
const conflictsTmp: ConflictsTmp = {};
|
const conflictsTmp: ConflictsTmp = {};
|
||||||
@@ -125,7 +131,18 @@ const Timetable = ({
|
|||||||
for (const td_index in tr.children) {
|
for (const td_index in tr.children) {
|
||||||
const td: HTMLTableCellElement = tr.children[td_index];
|
const td: HTMLTableCellElement = tr.children[td_index];
|
||||||
if (td.tagName !== "TD") continue;
|
if (td.tagName !== "TD") continue;
|
||||||
|
|
||||||
if (td.getAttribute("bgcolor")?.toUpperCase() !== "#39CEFF") {
|
if (td.getAttribute("bgcolor")?.toUpperCase() !== "#39CEFF") {
|
||||||
|
// const position (assigned by supervisor)
|
||||||
|
if (td?.textContent?.trim() === user) {
|
||||||
|
const constSelected = document.createElement("input");
|
||||||
|
constSelected.setAttribute("type", "checkbox");
|
||||||
|
constSelected.setAttribute("checked", "1");
|
||||||
|
constSelected.setAttribute("disabled", "1");
|
||||||
|
td.innerHTML = "";
|
||||||
|
td.appendChild(constSelected);
|
||||||
|
}
|
||||||
|
|
||||||
row.push(null);
|
row.push(null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -189,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";
|
||||||
@@ -209,11 +226,11 @@ const Timetable = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (disableNetwork) return;
|
if (disableNetwork || isRegular) return;
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
refresh();
|
refresh();
|
||||||
}, 1000);
|
}, 1500);
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
@@ -221,48 +238,62 @@ const Timetable = ({
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
const json = await get("/api/html");
|
const json = await get(isRegular ? "/api/html-regular" : "/api/html");
|
||||||
ref.current.innerHTML = json.html;
|
ref.current.innerHTML = json.html;
|
||||||
handleInput({ target: ref.current });
|
handleInput({ target: ref.current });
|
||||||
refresh();
|
refresh();
|
||||||
};
|
};
|
||||||
main();
|
main();
|
||||||
|
|
||||||
|
// Auto refresh the entire timetable every 3 minutes
|
||||||
|
const fullRefreshInterval = setInterval(() => {
|
||||||
|
main();
|
||||||
|
}, 3 * 60 * 1000); // 3 minutes in milliseconds
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(fullRefreshInterval);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const DownloadMarks = async () => {
|
||||||
|
console.log("download marks", marks);
|
||||||
|
const data = {
|
||||||
|
user,
|
||||||
|
selections: {},
|
||||||
|
};
|
||||||
|
for (const row of marks) {
|
||||||
|
for (const input of row) {
|
||||||
|
if (input === null) continue;
|
||||||
|
if (input.checked) {
|
||||||
|
data.selections[input.name] = 1;
|
||||||
|
} else if (parseFloat(input.value)) {
|
||||||
|
data.selections[input.name] = parseFloat(input.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(data);
|
||||||
|
downloadObjectAsJson(data, user);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<h2 style={{ textAlign: "center" }}>ITSC 学 生 助 理 抢 班 系 统</h2>
|
||||||
onClick={async () => {
|
<h3 style={{ textAlign: "center" }}>Login as {user}</h3>
|
||||||
console.log("download marks", marks);
|
<h3 style={{ textAlign: "center" }}>请勿在短时间内多次操作同一时段的选择,选择后请耐心等待状态更新以确认是否选择成功</h3>
|
||||||
const data = {
|
|
||||||
user,
|
|
||||||
selections: {},
|
|
||||||
};
|
|
||||||
for (const row of marks) {
|
|
||||||
for (const input of row) {
|
|
||||||
if (input === null) continue;
|
|
||||||
if (input.checked) {
|
|
||||||
data.selections[input.name] = 1;
|
|
||||||
} else if (parseFloat(input.value)) {
|
|
||||||
data.selections[input.name] = parseFloat(input.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(data);
|
|
||||||
downloadObjectAsJson(data, user);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
DownloadSelection
|
|
||||||
</button>
|
|
||||||
<span>Login as {user}</span>
|
|
||||||
<div
|
<div
|
||||||
|
align="center"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
contentEditable={editable}
|
contentEditable={editable}
|
||||||
style={{
|
style={{
|
||||||
overflow: "scroll",
|
overflow: "scroll",
|
||||||
}}
|
}}
|
||||||
onInput={handleInput}
|
onInput={handleInput}
|
||||||
></div>{" "}
|
></div>
|
||||||
|
{!hideDownloadButton && (
|
||||||
|
<p style={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<button onClick={DownloadMarks}>Download Selection</button>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<div style={{ display: "none" }} id="download-dom"></div>
|
<div style={{ display: "none" }} id="download-dom"></div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { NodeNextRequest } from "next/dist/server/base-http/node";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const UserInputWrap = ({ children, setUser }) => {
|
const UserInputWrap = ({ children, setUser }) => {
|
||||||
@@ -5,31 +6,52 @@ 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 (
|
||||||
<>
|
<>
|
||||||
{!begin && (
|
{!begin && (
|
||||||
<div>
|
<div
|
||||||
<input
|
style={{
|
||||||
placeholder="在这输你的名字"
|
display: "grid",
|
||||||
value={inputUser}
|
placeItems: "center",
|
||||||
onChange={(event) => setInputUser(event.target.value)}
|
alignItems: "center",
|
||||||
/>
|
// minHeight: "100vh",
|
||||||
<button
|
}}
|
||||||
onClick={() => {
|
>
|
||||||
if (inputUser.trim() === "") {
|
<h2 style={{ textAlign: "center" }}>ITSC 学 生 助 理 抢 班 系 统</h2>
|
||||||
alert("姓名不能为空");
|
<div>
|
||||||
return;
|
<input
|
||||||
}
|
style={{
|
||||||
setUser(inputUser.trim());
|
margin: "0.5em",
|
||||||
setBegin(true);
|
padding: "0.5em",
|
||||||
localStorage.setItem("user", inputUser.trim());
|
borderRadius: "0.39em",
|
||||||
}}
|
}}
|
||||||
>
|
placeholder="你的名字..."
|
||||||
Login
|
value={inputUser}
|
||||||
</button>
|
onChange={(event) => setInputUser(event.target.value)}
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
margin: "0.5em",
|
||||||
|
padding: "0.5em",
|
||||||
|
backgroundColor: "#39ceff",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (inputUser.trim() === "") {
|
||||||
|
alert("姓名不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUser(inputUser.trim());
|
||||||
|
setBegin(true);
|
||||||
|
localStorage.setItem("username", inputUser.trim());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{begin && children}
|
{begin && children}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const obj = {
|
const obj = {
|
||||||
begin: false,
|
begin: false,
|
||||||
limit: 2,
|
limit: 2,
|
||||||
token: process.env.TOKEN || "woshimima",
|
token: process.env.TOKEN ?? "woshimima",
|
||||||
};
|
};
|
||||||
export default obj;
|
export default obj;
|
||||||
|
|||||||
BIN
data-init.zip
Normal file
BIN
data-init.zip
Normal file
Binary file not shown.
@@ -4,9 +4,9 @@
|
|||||||
"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",
|
"start": "next start -p 4000",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||||||
import { store, html } from "@/store";
|
import { store, html } from "@/store";
|
||||||
import config from "@/config";
|
import config from "@/config";
|
||||||
|
|
||||||
export default function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse<Record<string, string>>
|
res: NextApiResponse<Record<string, string>>
|
||||||
) {
|
) {
|
||||||
@@ -16,5 +16,6 @@ export default function handler(
|
|||||||
const json = req.body;
|
const json = req.body;
|
||||||
store.update(json);
|
store.update(json);
|
||||||
}
|
}
|
||||||
|
await store.save();
|
||||||
res.status(200).json(store.get());
|
res.status(200).json(store.get());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import config from "@/config";
|
|||||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method === "POST") {
|
if (req.method === "POST") {
|
||||||
if (req.headers.token !== config.token) {
|
if (req.headers.token !== config.token) {
|
||||||
|
console.log("api::config: wrong token", req.headers.token, config.token);
|
||||||
res.status(403).json({ error: "wrong token" });
|
res.status(403).json({ error: "wrong token" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// update config
|
// update config
|
||||||
config.begin = req.body.begin ?? config.begin;
|
config.begin = req.body.begin ?? config.begin;
|
||||||
config.limit = req.body.limit ?? config.limit;
|
config.limit = req.body.limit ?? config.limit;
|
||||||
|
console.log("api::config: update config", config);
|
||||||
}
|
}
|
||||||
res.status(200).json(config);
|
res.status(200).json(config);
|
||||||
}
|
}
|
||||||
|
|||||||
16
pages/api/html-regular.ts
Normal file
16
pages/api/html-regular.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { htmlRegular } from "@/store";
|
||||||
|
import config from "@/config";
|
||||||
|
|
||||||
|
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
if (req.method === "POST") {
|
||||||
|
if (req.headers.token !== config.token) {
|
||||||
|
res.status(403).json({ error: "wrong token" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
htmlRegular.set(req.body.html);
|
||||||
|
}
|
||||||
|
res.status(200).json({
|
||||||
|
html: htmlRegular.get(),
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -5,15 +5,16 @@ import config from "@/config";
|
|||||||
|
|
||||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const store = storeProxy.get();
|
const store = storeProxy.get();
|
||||||
|
// console.log("api::store.null()", store);
|
||||||
if (req.method === "POST") {
|
if (req.method === "POST") {
|
||||||
if (!config.begin) {
|
if (!config.begin) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
error: "还没到开时间哦",
|
error: "还没到开始时间哦",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const json = req.body;
|
const json = req.body;
|
||||||
console.log("request", json);
|
console.log("api::request: new request", json);
|
||||||
if (json.checked) {
|
if (json.checked) {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (const name in store) {
|
for (const name in store) {
|
||||||
@@ -21,33 +22,42 @@ 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 alreadly 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;
|
||||||
}
|
}
|
||||||
delete store[json.name];
|
delete store[json.name];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("query", req.query);
|
const resp: { occupied: string[], myselect: string[] } = { // try to fix
|
||||||
const resp = {
|
|
||||||
occupied: [],
|
occupied: [],
|
||||||
myselect: [],
|
myselect: [],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const read = promisify(fs.readFile);
|
|||||||
|
|
||||||
// 索引与工时
|
// 索引与工时
|
||||||
const indexToHour: Record<string, number> = JSON.parse(
|
const indexToHour: Record<string, number> = JSON.parse(
|
||||||
fs.readFileSync("./hours.json", "utf8")
|
fs.readFileSync("./data/hours.json", "utf8")
|
||||||
).selections;
|
).selections;
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
@@ -18,7 +18,7 @@ export default async function handler(
|
|||||||
// 读入全部json文件
|
// 读入全部json文件
|
||||||
// users: {姓名: {坐标: 权重}}
|
// users: {姓名: {坐标: 权重}}
|
||||||
const users: Record<string, Record<string, number>> = {};
|
const users: Record<string, Record<string, number>> = {};
|
||||||
const files = await g("./json/*.json");
|
const files = await g("./data/json/*.json");
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const jsonStr = await read(file, "utf8");
|
const jsonStr = await read(file, "utf8");
|
||||||
const json: {
|
const json: {
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { get, post } from "@/common";
|
|||||||
const ControlPage = () => {
|
const ControlPage = () => {
|
||||||
const [isBegin, setIsBegin] = React.useState(false);
|
const [isBegin, setIsBegin] = React.useState(false);
|
||||||
const [inputLimit, setInputLimit] = React.useState("2");
|
const [inputLimit, setInputLimit] = React.useState("2");
|
||||||
|
const [token, setToken] = React.useState("");
|
||||||
const toggleBegin = async () => {
|
const toggleBegin = async () => {
|
||||||
const json = await post("/api/config", { begin: !isBegin });
|
const json = await post("/api/config", { begin: !isBegin }, { token });
|
||||||
setIsBegin(json.begin);
|
setIsBegin(json.begin);
|
||||||
};
|
};
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
@@ -26,9 +27,16 @@ const ControlPage = () => {
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
<main>
|
<main>
|
||||||
|
<p>
|
||||||
|
<input
|
||||||
|
value={token}
|
||||||
|
onChange={(event) => setToken(event.target.value)}
|
||||||
|
placeholder="Token"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<button onClick={() => toggleBegin()}>
|
<button onClick={() => toggleBegin()}>
|
||||||
{isBegin ? "Begin" : "Pause"}
|
{isBegin ? "Pause" : "Begin"}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -40,9 +48,13 @@ const ControlPage = () => {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
post("/api/config", {
|
post(
|
||||||
limit: parseInt(inputLimit) || 2,
|
"/api/config",
|
||||||
});
|
{
|
||||||
|
limit: parseInt(inputLimit) || 2,
|
||||||
|
},
|
||||||
|
{ token }
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Set Limit
|
Set Limit
|
||||||
|
|||||||
39
pages/edit-regular.tsx
Normal file
39
pages/edit-regular.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { get, post } from "@/common";
|
||||||
|
|
||||||
|
const EditPage = () => {
|
||||||
|
const [token, setToken] = React.useState("");
|
||||||
|
const ref = React.useRef();
|
||||||
|
const upload = async () => {
|
||||||
|
const html = ref.current.innerHTML;
|
||||||
|
await post("/api/html-regular", { html }, { token });
|
||||||
|
alert("Upload success");
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
const refresh = async () => {
|
||||||
|
const html = await get("/api/html-regular");
|
||||||
|
ref.current.innerHTML = html.html;
|
||||||
|
};
|
||||||
|
React.useEffect(() => {
|
||||||
|
refresh();
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
value={token}
|
||||||
|
placeholder={"token"}
|
||||||
|
onChange={(event) => setToken(event.target.value)}
|
||||||
|
/>
|
||||||
|
<button onClick={() => upload()}>Upload</button>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
onInput={(event) => {
|
||||||
|
console.log(event.currentTarget.innerHTML);
|
||||||
|
}}
|
||||||
|
contentEditable="true"
|
||||||
|
></div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditPage;
|
||||||
@@ -9,9 +9,9 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Create Next App</title>
|
<title>抢 班</title>
|
||||||
<meta name="description" content="Generated by create next app" />
|
<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>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const ReportPage = () => {
|
|||||||
disableNetwork={true}
|
disableNetwork={true}
|
||||||
apiRecordEndPoint="/api/regular"
|
apiRecordEndPoint="/api/regular"
|
||||||
openRecordMode={true}
|
openRecordMode={true}
|
||||||
|
isRegular={true}
|
||||||
/>
|
/>
|
||||||
</UserInputWrap>
|
</UserInputWrap>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -47,6 +47,7 @@ export default function Home() {
|
|||||||
disableConflictCheck={true}
|
disableConflictCheck={true}
|
||||||
disableNetwork={true}
|
disableNetwork={true}
|
||||||
replaceInputType={"number"}
|
replaceInputType={"number"}
|
||||||
|
hideDownloadButton={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -26,16 +26,28 @@ class Store {
|
|||||||
delete this.record[key];
|
delete this.record[key];
|
||||||
}
|
}
|
||||||
public async update(record: Record<string, string>) {
|
public async update(record: Record<string, string>) {
|
||||||
await write(this.filename, JSON.stringify(this.record), "utf8");
|
this.record = record;
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
public async save() {
|
||||||
|
// try first, then catch
|
||||||
|
try {
|
||||||
|
console.log("store::index: save record", this.record);
|
||||||
|
await write(this.filename, JSON.stringify(this.record), "utf8");
|
||||||
|
} catch {
|
||||||
|
console.error("store::index: save record error, filename:", this.filename);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HTML {
|
class HTML {
|
||||||
html: string;
|
html: string;
|
||||||
constructor() {
|
filename: string;
|
||||||
|
constructor(filename: string) {
|
||||||
|
this.filename = filename;
|
||||||
// load from file
|
// load from file
|
||||||
try {
|
try {
|
||||||
this.html = fs.readFileSync("./html.html", "utf8");
|
this.html = fs.readFileSync(this.filename, "utf8");
|
||||||
} catch {
|
} catch {
|
||||||
this.html = "";
|
this.html = "";
|
||||||
}
|
}
|
||||||
@@ -46,10 +58,16 @@ class HTML {
|
|||||||
public async set(html: string) {
|
public async set(html: string) {
|
||||||
this.html = html;
|
this.html = html;
|
||||||
// store into file
|
// store into file
|
||||||
await write("./html.html", html, "utf8");
|
// try first, then catch
|
||||||
|
try {
|
||||||
|
await write(this.filename, html, "utf8");
|
||||||
|
} catch {
|
||||||
|
console.error("store::index: save record error, filename:", this.filename);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const html = new HTML();
|
export const html = new HTML("./data/html-current.html");
|
||||||
export const store = new Store("store.json");
|
export const htmlRegular = new HTML("./data/html-regular.html");
|
||||||
export const regular = new Store("regular.json");
|
export const store = new Store("./data/store.json");
|
||||||
|
export const regular = new Store("./data/regular.json");
|
||||||
|
|||||||
Reference in New Issue
Block a user