support limit

This commit is contained in:
2022-11-23 18:08:04 +08:00
parent bfe739e442
commit f4cb36dbee
5 changed files with 108 additions and 28 deletions

View File

@@ -13,6 +13,7 @@ type Timetable struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Status bool `json:"status"` Status bool `json:"status"`
Limit int64 `json:"limit"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
} }
@@ -51,6 +52,7 @@ var (
UpdateTakeCount *sql.Stmt UpdateTakeCount *sql.Stmt
CheckTableStatus *sql.Stmt CheckTableStatus *sql.Stmt
CheckLimit *sql.Stmt
) )
func init() { func init() {
@@ -60,7 +62,7 @@ func init() {
log.Fatal(err) log.Fatal(err)
} }
if len(os.Args) > 1{ if len(os.Args) > 1 {
if os.Args[1] == "install" { if os.Args[1] == "install" {
install() install()
os.Exit(0) os.Exit(0)
@@ -68,7 +70,7 @@ func init() {
} }
GetAllTimetables, err = DB.Prepare(` GetAllTimetables, err = DB.Prepare(`
select id, name, status select id, name, status, "limit"
from timetables from timetables
order by status desc, created desc order by status desc, created desc
@@ -77,8 +79,8 @@ order by status desc, created desc
log.Fatal(err) log.Fatal(err)
} }
CreateNewTimetable, err = DB.Prepare(` CreateNewTimetable, err = DB.Prepare(`
insert into timetables (name) insert into timetables (name, "limit")
values ($1) values ($1, $2)
returning id returning id
`) `)
if err != nil { if err != nil {
@@ -93,7 +95,8 @@ where id = $1
} }
GetTimeSlotsByTimetable, err = DB.Prepare(` GetTimeSlotsByTimetable, err = DB.Prepare(`
select t.id, t."name" ,t."time" ,t.take, t.capacity, t2."name", t2.status, select t.id, t."name" ,t."time" ,t.take, t.capacity, t2."name", t2.status,
case sub.username when $2 then true else false end as success case sub.username when $2 then true else false end as success,
t2."limit"
from timeslots t from timeslots t
join timetables t2 on t.ttid = t2.id join timetables t2 on t.ttid = t2.id
left outer join ( left outer join (
@@ -137,8 +140,8 @@ where tsid = $1 and username = $2
} }
UpdateTimetableStatus, err = DB.Prepare(` UpdateTimetableStatus, err = DB.Prepare(`
update timetables update timetables
set status = $1 set status = $1, "limit" = $2
where id = $2 where id = $3
`) `)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@@ -171,4 +174,15 @@ select status from timetables where id = $1
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
CheckLimit, err = DB.Prepare(`
select count(t1.username) < t3."limit" as c
from takes t1
join timeslots t2 on t1.tsid = t2.id
join timetables t3 on t2.ttid = t3.id
where t1.username = $1 and t3.id = $2
group by t1.username, t3."limit"
`)
if err != nil {
log.Fatal(err)
}
} }

View File

@@ -12,9 +12,10 @@ func install() {
} }
_, err = tx.Exec(` _, err = tx.Exec(`
CREATE TABLE timetables ( CREATE TABLE timetables (
id serial primary key, id serial primary key,
"name" text NOT NULL, "name" text NOT NULL,
"limit" integer NOT NULL DEFAULT 1,
status bool NOT NULL DEFAULT false, status bool NOT NULL DEFAULT false,
created timestamp NOT NULL DEFAULT now() created timestamp NOT NULL DEFAULT now()
); );

28
main.go
View File

@@ -72,7 +72,7 @@ func main() {
} }
for rows.Next() { for rows.Next() {
s := &db.Timetable{} s := &db.Timetable{}
rows.Scan(&s.ID, &s.Name, &s.Status) rows.Scan(&s.ID, &s.Name, &s.Status, &s.Limit)
timetables = append(timetables, s) timetables = append(timetables, s)
} }
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
@@ -82,7 +82,8 @@ func main() {
api.POST("/timetables", auth, func(c *gin.Context) { api.POST("/timetables", auth, func(c *gin.Context) {
type Request struct { type Request struct {
Name string `json:"newTimeTableName"` Name string `json:"newTimeTableName"`
Limit int64 `json:"newLimit"`
} }
req := &Request{} req := &Request{}
err := c.ShouldBindBodyWith(req, binding.JSON) err := c.ShouldBindBodyWith(req, binding.JSON)
@@ -90,7 +91,7 @@ func main() {
c.AbortWithError(400, err) c.AbortWithError(400, err)
return return
} }
row := db.CreateNewTimetable.QueryRow(req.Name) row := db.CreateNewTimetable.QueryRow(req.Name, req.Limit)
var id int64 var id int64
err = row.Scan(&id) err = row.Scan(&id)
if err != nil { if err != nil {
@@ -130,9 +131,10 @@ func main() {
timeslots := make([]*db.TimeSlot, 0) timeslots := make([]*db.TimeSlot, 0)
var timetableName string var timetableName string
var timetableStatus bool var timetableStatus bool
var timetableLimit int64
for rows.Next() { for rows.Next() {
s := &db.TimeSlot{} s := &db.TimeSlot{}
err = rows.Scan(&s.ID, &s.Name, &s.Time, &s.Take, &s.Capacity, &timetableName, &timetableStatus, &s.Success) err = rows.Scan(&s.ID, &s.Name, &s.Time, &s.Take, &s.Capacity, &timetableName, &timetableStatus, &s.Success, &timetableLimit)
if err != nil { if err != nil {
c.AbortWithError(400, err) c.AbortWithError(400, err)
return return
@@ -143,6 +145,7 @@ func main() {
"timeslots": timeslots, "timeslots": timeslots,
"timetableName": timetableName, "timetableName": timetableName,
"timetableStatus": timetableStatus, "timetableStatus": timetableStatus,
"timetableLimit": timetableLimit,
}) })
}) })
@@ -256,7 +259,7 @@ func main() {
c.AbortWithError(400, err) c.AbortWithError(400, err)
return return
} }
_, err = db.UpdateTimetableStatus.Exec(req.Status, timetableID) _, err = db.UpdateTimetableStatus.Exec(req.Status, req.Limit, timetableID)
if err != nil { if err != nil {
c.AbortWithError(400, err) c.AbortWithError(400, err)
return return
@@ -291,6 +294,21 @@ func main() {
tx.Rollback() tx.Rollback()
return return
} }
can := true
row, err := db.CheckLimit.Query(req.Username, timetableID)
for row.Next() {
err = row.Scan(&can)
}
if err != nil {
c.AbortWithError(400, err)
tx.Rollback()
return
}
if !can {
c.AbortWithError(401, errors.New("超出报名数量限制"))
tx.Rollback()
return
}
updateCountStmt := tx.Stmt(db.UpdateTakeCount) updateCountStmt := tx.Stmt(db.UpdateTakeCount)
if req.Username[0] == '!' { if req.Username[0] == '!' {
untakeStmt := tx.Stmt(db.UserUntakeTimeslot) untakeStmt := tx.Stmt(db.UserUntakeTimeslot)

View File

@@ -22,6 +22,7 @@ const Home = () => {
const [timetables, setTimetables] = React.useState([]); const [timetables, setTimetables] = React.useState([]);
const [newTimetableName, setNewTimetableName] = React.useState(""); const [newTimetableName, setNewTimetableName] = React.useState("");
const [newLimit, setNewLimit] = React.useState(null);
const navigator = useNavigate(); const navigator = useNavigate();
const fetchData = async () => { const fetchData = async () => {
@@ -48,6 +49,18 @@ const Home = () => {
}} }}
placeholder="班表名" placeholder="班表名"
/> />
<TextField
value={newLimit}
onChange={(event) => {
let num = parseInt(event.target.value)
if (num <= 0) {
num = 1;
}
setNewLimit(num);
}}
placeholder="每人选择上限"
type="number"
/>
<Button <Button
variant="contained" variant="contained"
color="secondary" color="secondary"
@@ -55,6 +68,7 @@ const Home = () => {
await post("/timetables", { await post("/timetables", {
token, token,
newTimetableName, newTimetableName,
newLimit,
}); });
await fetchData(); await fetchData();
}} }}

View File

@@ -18,10 +18,11 @@ import { UsernameContext } from "./Login";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { get, post, put } from "../fetches"; import { get, post, put } from "../fetches";
const AdminComponent = ({ refresh, timetableStatus }) => { const AdminComponent = ({ refresh, timetableStatus, timetableLimit }) => {
const { username, token } = React.useContext(UsernameContext); const { username, token } = React.useContext(UsernameContext);
const [newName, setNewName] = React.useState(""); const [newName, setNewName] = React.useState("");
const [newTime, setNewTime] = React.useState(""); const [newTime, setNewTime] = React.useState("");
const [newLimit, setNewLimit] = React.useState(timetableLimit);
const [newCapacity, setNewCapacity] = React.useState(1); const [newCapacity, setNewCapacity] = React.useState(1);
const params = useParams(); const params = useParams();
let { timetableID } = params; let { timetableID } = params;
@@ -34,17 +35,11 @@ const AdminComponent = ({ refresh, timetableStatus }) => {
}); });
await refresh(); await refresh();
}; };
const handleOpen = async () => { const handleChangeStatus = async (status, limit) => {
await put(`/timetables/${timetableID}`, { await put(`/timetables/${timetableID}`, {
token, token,
status: true, limit,
}); status,
await refresh();
};
const handleClose = async () => {
await put(`/timetables/${timetableID}`, {
token,
status: false,
}); });
await refresh(); await refresh();
}; };
@@ -85,15 +80,47 @@ const AdminComponent = ({ refresh, timetableStatus }) => {
添加 添加
</Button> </Button>
{timetableStatus && ( {timetableStatus && (
<Button variant="contained" color="warning" onClick={handleClose}> <Button
variant="contained"
color="warning"
onClick={() => {
handleChangeStatus(false, timetableLimit);
}}
>
暂停报名 暂停报名
</Button> </Button>
)} )}
{!timetableStatus && ( {!timetableStatus && (
<Button variant="contained" color="success" onClick={handleOpen}> <Button
variant="contained"
color="success"
onClick={() => {
handleChangeStatus(true, timetableLimit);
}}
>
开放报名 开放报名
</Button> </Button>
)} )}
<TextField
value={newLimit}
onChange={(event) => {
let number = parseInt(event.target.value);
if (number <= 0) {
number = 1;
}
setNewLimit(number);
}}
type="number"
/>
<Button
variant="contained"
color="primary"
onClick={() => {
handleChangeStatus(timetableStatus, newLimit);
}}
>
修改报名限制
</Button>
</Stack> </Stack>
) )
); );
@@ -103,6 +130,7 @@ const Timetable = () => {
const [timeslots, setTimeslots] = React.useState([]); const [timeslots, setTimeslots] = React.useState([]);
const [timetableName, setTimetableName] = React.useState(""); const [timetableName, setTimetableName] = React.useState("");
const [timetableStatus, setTimetableStatus] = React.useState(true); const [timetableStatus, setTimetableStatus] = React.useState(true);
const [timetableLimit, setTimetableLimit] = React.useState(null);
const { username } = React.useContext(UsernameContext); const { username } = React.useContext(UsernameContext);
const navigator = useNavigate(); const navigator = useNavigate();
const params = useParams(); const params = useParams();
@@ -115,6 +143,7 @@ const Timetable = () => {
setTimeslots(data.timeslots); setTimeslots(data.timeslots);
setTimetableName(data.timetableName); setTimetableName(data.timetableName);
setTimetableStatus(data.timetableStatus); setTimetableStatus(data.timetableStatus);
setTimetableLimit(data.timetableLimit);
}; };
const handleTakeSlot = async (slotID, status) => { const handleTakeSlot = async (slotID, status) => {
if (username === "admin") { if (username === "admin") {
@@ -129,7 +158,7 @@ const Timetable = () => {
} }
}; };
React.useEffect(() => { React.useEffect(() => {
refresh() refresh();
const interval = setInterval(() => { const interval = setInterval(() => {
refresh(); refresh();
}, 1000); }, 1000);
@@ -150,9 +179,13 @@ const Timetable = () => {
</Button> </Button>
<Typography variant="h4">{timetableName}</Typography> <Typography variant="h4">{timetableName}</Typography>
<AdminComponent refresh={refresh} timetableStatus={timetableStatus} /> <AdminComponent
refresh={refresh}
timetableStatus={timetableStatus}
timetableLimit={timetableLimit}
/>
<Typography>列表实时更新中</Typography> <Typography>列表实时更新中每人报名限制{timetableLimit}</Typography>
<LinearProgress /> <LinearProgress />
<TableContainer> <TableContainer>
<Table> <Table>