设备申请模块 — 后端和数据库实现步骤指南

说明 内容
文档版本 v1.0
适用对象 具备基础 SQL、Python、FastAPI 知识
任务范围 仅后端 + 数据库;前端交给 AI 按第 7 章接口契约对接
需求依据 设备申请模块 — 产品需求文档(PRD)
预计工时 10 ~ 14 天(边做边学)

使用说明

  1. 按章节顺序做。尤其先做「管理端设备录入」,再做「用户申请」——没有设备数据,后面无法测试。

  2. 没有提供完整代码,只提供步骤说明和伪代码。

第 0 章 开始前必读

0.1 要做成什么

用一句话概括:

管理员在后台录入设备 → 师生在用户端看到设备并提交借用申请 → 管理员审核 → 用户查看结果 / 撤回待审申请。

当前缺口:

已有 缺失
用户端设备申请 UI(但是 Mock 数据) 设备相关后端 API
devices、device_bookings 数据库表 device_bookings 部分字段
管理端 /admin/devices、/admin/device-bookings 菜单 两个页面都是占位,无 API

0.2 项目后端结构(先建立整体印象)

1
2
3
4
5
6
7
8
9
10
11
12
13
backend/
├── main.py # 入口:注册路由
└── app/
├── api/routes/
│ ├── appointments.py # ★ 预约模块:主要参照模板
│ ├── labs.py # ★ 简单列表查询参照
│ ├── devices.py # ☆ 要新建:设备台账
│ └── device_bookings.py # ☆ 要新建:借用申请
├── models/
│ ├── device.py # 设备表 ORM
│ └── device_booking.py # 申请表 ORM(需补字段)
└── core/
└── database.py # 数据库连接、建表、补列

0.3 必读参照文件

这一步我觉得比较重要,动手前先浏览一遍现有文件,包括机房预约:appointments.py,labs.py。以及设备申请相关的初始代码(看懂大致流程即可。不必每行都懂,看不懂的地方可以问TRAE,或者问我):

文件 文件说明 学什么
backend/app/api/routes/appointments.py 实验室预约的 HTTP API 路由,处理前端/管理端的预约请求。 列表分页、POST 创建、PATCH 审核
backend/app/api/routes/labs.py 实验室信息的 只读 API,给预约页展示「有哪些实验室可以约」。 最简单的 GET 列表
backend/app/models/device.py 定义数据库表 devices(设备台账)的 Python 模型。描述「系统里有哪些设备、库存多少、什么状态」。 设备表有哪些字段
backend/app/models/device_booking.py 定义数据库表 device_bookings(设备借用申请单)的 Python 模型。用户申请借设备时,每条申请存一张「工单」。 申请表现有字段
backend/app/models/appointment.py 定义数据库表 appointments(实验室预约记录)的 Python 模型,告诉 SQLAlchemy:预约一条记录要存哪些字段、什么类型。 对照 admin_remark、reject_reason 怎么定义

0.4 实施顺序总览

1
2
3
4
5
6
7
第1章  补全 device_bookings 表字段
第2章 设备台账 API(管理端录入)← 数据源头,最先做
第3章 用户端设备列表 GET
第4章 提交借用申请 POST
第5章 申请列表 + 审核 + 撤回
第6章 注册路由 + 完整流程自测
第7章 交给 AI 的前端对接说明

0.5 文件改动 Checklist(做完打勾)

  • backend/app/models/device_booking.py — 增加缺失字段

  • backend/app/core/database.py — 补列函数(可选)

  • backend/app/api/routes/devices.py — 新建(含 CRUD)

  • backend/app/api/routes/device_bookings.py — 新建

  • backend/main.py — 注册两个新路由

第 1 章 数据库:补全 device_bookings 表字段

1.1 为什么要改表

用户端申请表单要填:学号、姓名、班级、专业、电话、教师、事由等。
当前 device_bookings 表只有 user_name,不够用。
审核还需要 admin_remark(管理员备注)和 reject_reason(拒绝原因),参照 appointments 表。

1.2 需要新增的字段

打开 backend/app/models/device_booking.py,在 DeviceBooking 类中增加以下列定义(具体 Column 写法参照 appointment.py):

字段名 类型建议 说明
device_name String(128) 设备名称
serial_number String(64) 设备编号
student_id String(64) 学号
class_name String(64) 班级
major String(64) 专业
phone String(32) 电话
teacher_id String(64) 指导教师 ID
teacher_name String(64) 指导教师姓名
admin_remark Text 管理员备注
reject_reason Text 拒绝原因

同时确认:user_name 用来存申请人姓名(对应表单 name 字段)。下面举几个例子说明代码具体如何写:橙色高亮的代码就是新增代码。例如根据表格新增device_name,类型是String(128),说明是设备名称,所以增加的代码就是device_name = Column(String(128), nullable=False, comment=”设备名字”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"""设备申请表/设备借用工单表模型"""
from sqlalchemy import Column, Integer, String, Text, SmallInteger, DateTime, Date
from sqlalchemy.sql import func

from app.core.database import Base


class DeviceBooking(Base):
__tablename__ = "device_bookings"

id = Column(Integer, primary_key=True, autoincrement=True)
device_id = Column(Integer, nullable=False, comment="关联设备id")
device_name = Column(String(128), nullable=False, comment="设备名字")
serial_number = Column(String(64), nullable=True, comment="设备序列号")
quantity = Column(Integer, nullable=False, default=1, comment="申请借用数量")
start_date = Column(Date, nullable=False, comment="开始日期")
end_date = Column(Date, nullable=False, comment="结束日期")
reason = Column(Text, nullable=False, comment="借用/使用原因")
special_requirement = Column(Text, nullable=True, comment="申请特殊需要,可为空")
user_id = Column(Integer, nullable=True, comment="用户ID")
user_name = Column(String(64), nullable=False, default="", comment="用户姓名")
# 0: 待审核 1: 已通过 2: 已拒绝 3: 已取消
status = Column(SmallInteger, nullable=False, default=0, comment="工单状态: 0待审核 1已通过 2已拒绝 3已取消")
created_at = Column(DateTime(timezone=True), server_default=func.now(), comment="记录创建时间")
openid = Column(String(128), nullable=False, default="", comment="用户openid")
class_name = Column(String(64), nullable=False, comment="班级")
major = Column(String(64), nullable=False, comment="专业")
phone = Column(String(32), nullable=False, comment="手机号")
teacher_id = Column(String(64), nullable=True, comment="教师ID")
teacher_name = Column(String(64), nullable=False, comment="教室名字")
admin_remark = Column(Text, nullable=True, comment="审核员的评论")
reject_reason = Column(Text, nullable=True, comment="审核员输入的拒绝原因")

1.3 让数据库真正拥有这些列

本项目启动时会执行 init_db()。按照下面流程来操作:

  1. 改好 device_booking.py 模型

  2. 用数据库客户端删除 device_bookings 表

  3. 重启后端,create_all 会按新模型建表

1.4 本章自测

检查项 怎么做 期望
模型无语法错误 重启后端不报错 终端正常启动
表结构正确 SQL:DESCRIBE device_bookings; 能看到新增列

1.5 常见错误

现象 原因 处理
启动报 Unknown column 模型加了字段但表没更新 删表重建或写补列函数
字段名对不上 Python 用下划线,JSON 用驼峰 后面 API 层做映射,本章只管数据库

第 2 章 设备台账 API(管理端录入)★ 优先完成

本章是整个模块的数据源头。 管理员录入的设备,才会出现在用户端供师生申请。

2.1 新建路由文件

创建文件:backend/app/api/routes/devices.py

文件开头的代码(这是伪代码,可以把伪代码转换成python代码):

1
2
3
4
5
6
7
从 fastapi 导入 APIRouter, Depends, HTTPException, Query
从 pydantic 导入 BaseModel, Field
从 sqlalchemy.orm 导入 Session
从 app.core.database 导入 get_db
从 app.models 导入 Device

router = APIRouter(prefix="/devices", tags=["devices"])

2.2 编写「设备对象转字典」辅助函数

前端需要的字段名和数据库不完全一样,需要一个转换函数。

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
函数 device_to_dict(device行, 是否管理端模式):
计算对外状态 status_text:
如果 device.status != 0 → "maintenance" 或 "retired"(按 1/2 区分)
否则如果 available_quantity <= 0 → "unavailable"
否则 → "available"

返回字典:
id = device.id
name = device.name
code = device.serial_number # 注意:DB 叫 serial_number,前端叫 code
spec = device.description 或截断后的描述
category = device.category
manufacturer = device.manufacturer
model = device.model
location = device.location
department = device.department
total_quantity = device.total_quantity
available_quantity = device.available_quantity
status = status_text # 对外字符串,不是数字 0/1/2
manager = device.manager
icon = device.icon

2.3 定义 Pydantic 请求体(录入 / 编辑用)

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
类 DeviceCreateBody:
name: 字符串,必填,最少1字
serialNumber: 字符串,必填
category: 整数,0~3
manufacturer: 字符串,默认空
model: 字符串,默认空
description: 字符串,默认空
location: 字符串,默认空
department: 字符串,默认空
totalQuantity: 整数,默认1,>=1
availableQuantity: 整数,可选;若不传则等于 totalQuantity
status: 整数,默认0(0正常 1维修 2报废)
manager: 字符串,默认空
icon: 字符串,默认空

类 DeviceUpdateBody:
字段与 DeviceCreateBody 相同(或全部可选,P0 可以要求全量更新)

说明:前端 JSON 常用驼峰(serialNumber),数据库用下划线(serial_number),在写入时做转换。

2.4 实现 GET /devices 接口(列表)

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
GET /devices
参数:
category: 可选整数
q: 可选搜索关键字
page: 默认1
page_size: 默认10,最大100
scope: 可选,"public" 或 "admin",默认 "public"

函数 list_devices(db, category, q, page, page_size, scope):
query = db.query(Device)

# 用户端只看待借设备
如果 scope != "admin":
query = query.filter(Device.status == 0)

如果 category 有值:
query = query.filter(Device.category == category)

如果 q 非空:
like = "%" + q + "%"
query = query.filter(
Device.name 像 like
或 Device.serial_number 像 like
或 Device.description 像 like
或 Device.model 像 like
)

total = query.count()
rows = query.order_by(Device.id.desc())
.offset((page-1)*page_size)
.limit(page_size)
.all()

返回 {
data: [device_to_dict(r, scope=="admin") for r in rows],
pagination: { page, page_size, total, total_pages }
}

2.5 实现 GET /devices/{id}(详情)

伪代码:

1
2
3
4
5
GET /devices/{device_id}

row = 按 id 查询 Device
如果不存在 → HTTP 404 "未找到该设备"
返回 device_to_dict(row)

2.6 实现 POST /devices(管理端新增录入)★

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
POST /devices
请求体: DeviceCreateBody

函数 create_device(body, db):
# 校验
如果 body.name 去空格后为空 → 400
如果 body.serialNumber 去空格后为空 → 400
如果 body.totalQuantity < 1 → 400

avail = body.availableQuantity
如果 avail 未传或为 None:
avail = body.totalQuantity
如果 avail < 0 或 avail > body.totalQuantity → 400

如果 body.category 不在 0~3 → 400
如果 body.status 不在 0~2 → 400

# 编号唯一(建议)
已存在 = db.query(Device).filter(serial_number == body.serialNumber).first()
如果已存在 → 400 "设备编号已存在"

# 写入
row = Device(
name = body.name.strip(),
serial_number = body.serialNumber.strip(),
category = body.category,
manufacturer = body.manufacturer.strip(),
model = body.model.strip(),
description = body.description.strip(),
location = body.location.strip(),
department = body.department.strip(),
total_quantity = body.totalQuantity,
available_quantity = avail,
status = body.status,
manager = body.manager.strip(),
icon = body.icon.strip(),
)
db.add(row)
db.commit()
db.refresh(row)
返回 { ok: true, id: row.id }

2.7 实现 PUT /devices/{id}(管理端编辑)

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
PUT /devices/{device_id}
请求体: DeviceUpdateBody(字段同创建)

row = 按 id 查询,不存在 → 404

重复校验 serialNumber 唯一(排除自身 id)

重复数量校验 available <= total

更新 row 各字段(从 body 赋值)
db.commit()
返回 { ok: true, data: device_to_dict(row) }

2.8 管理端「下架」怎么做(P0 不做 DELETE)-- 先不用做

PRD 建议用软下架,不物理删除记录:

  • 把 status 改为 2(报废),或

  • 把 available_quantity 改为 0

用户端 scope=public 时自然看不到或显示不可借。

如果一定要做 DELETE 接口(可选):

1
2
3
4
DELETE /devices/{id}
检查 device_bookings 有没有该 device_id 且 status 为 0 或 1 的记录
如果有 → 400 "存在进行中申请,不能删除"
否则 db.delete(row); commit

2.9 在 main.py 注册 devices 路由

伪代码(在 main.py):

1
2
import devices 路由模块
app.include_router(devices.router, prefix=API_PREFIX)

2.10 本章自测(用 Swagger:http://localhost:8000/docs)

按顺序测:

步骤 操作 期望
2a POST /api/devices 录入一台笔记本电脑 category=0 返回 { ok: true, id: N }
2b POST 再录入一台实验器材 category=2 成功
2c 用相同 serialNumber 再 POST 400 编号重复
2d GET /api/devices?scope=admin 能看到全部,含 status
2e GET /api/devices(默认 public) 只看到 status=0 的设备
2f GET /api/devices?category=2 只有实验器材
2g PUT /api/devices/{id} 改 available_quantity=0 成功
2h GET public 列表 该设备 status 为 unavailable
2i SQL:SELECT * FROM devices; 与 API 一致

2.11 常见错误

现象 原因
404 路由未在 main.py 注册
422 请求 JSON 字段名或类型与 Pydantic 不一致
500 数据库错误 模型字段与表结构不一致

第 3 章 用户端设备列表(只读,已在第 2 章实现了)

第 2 章的 GET /api/devices(scope=public)已经服务用户端。本章确认行为符合 PRD:

规则 说明
只展示 status=0 维修中、报废不展示给用户
可借判定 available_quantity > 0 → 前端显示「选择」
类别映射 category 0/1/2/3 对应 UI 四个 Tab

自测: 不传 scope 调用 GET,应看不到 status=2 的报废设备。

第 4 章 设备借用申请 API(POST 创建)

4.1 新建路由文件

创建:backend/app/api/routes/device_bookings.py

结构参照 appointments.py:

1
router = APIRouter(prefix="/device-bookings", tags=["device-bookings"])

4.2 定义请求体

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
类 DeviceBookingCreateBody:
deviceId: 整数,必填
quantity: 整数,默认 1
studentId: 字符串,默认空
name: 字符串,默认空 # 存入 user_name
className: 字符串,默认空
major: 字符串,默认空
phone: 字符串,必填
teacherId: 字符串,默认空
teacherName: 字符串,默认空
borrowStart: 字符串,日期 YYYY-MM-DD,必填
borrowEnd: 字符串,日期 YYYY-MM-DD,必填
reason: 字符串,必填

4.3 实现 POST /device-bookings

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
POST /device-bookings

函数 create_device_booking(body, db):
# 1. 查设备
device = db.query(Device).filter(id == body.deviceId).first()
如果不存在 → 404 "设备不存在"

# 2. 可借校验
如果 device.status != 0 → 400 "设备当前不可借"
如果 device.available_quantity < body.quantity → 400 "可借数量不足"

# 3. 日期校验
如果 body.borrowEnd < body.borrowStart → 400 "归还日期不能早于开始日期"

# 4. 必填校验
如果 body.phone 为空 → 400
如果 body.reason 为空 → 400

# 5. 创建申请记录
row = DeviceBooking(
device_id = device.id,
device_name = device.name,
serial_number = device.serial_number,
quantity = body.quantity,
start_date = 解析 body.borrowStart 为 Date,
end_date = 解析 body.borrowEnd 为 Date,
reason = body.reason.strip(),
student_id = body.studentId.strip(),
user_name = body.name.strip(),
class_name = body.className.strip(),
major = body.major.strip(),
phone = body.phone.strip(),
teacher_id = body.teacherId.strip(),
teacher_name = body.teacherName.strip(),
status = 0,
admin_remark = "",
reject_reason = "",
openid = "",
user_id = None,
)
db.add(row)
db.commit()
db.refresh(row)
返回 { ok: true, id: row.id }

说明:

  • start_date / end_date 在模型里是 Date 类型,需要把字符串转成 date 对象(可用 datetime.strptime 或 date.fromisoformat)。

  • P0 不扣减 available_quantity,只在提交时校验够用即可。

4.4 本章自测

步骤 操作 期望
4a 用第 2 章录入的设备 id 提交申请 200,device_bookings 新增一行 status=0
4b 用不存在的 deviceId 404
4c borrowEnd 早于 borrowStart 400
4d 把设备 available_quantity 改为 0 后再提交 400 库存不足

第 5 章 申请列表、审核、撤回

5.1 实现 GET /device-bookings(列表)

强烈建议: 打开 appointments.py 的 list_appointments,对照改写。

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
GET /device-bookings
参数:
q: 搜索关键字,可选
status: 整数 0/1/2/3,可选
date_from, date_to: 可选,按 start_date 或 created_at 筛选
page, page_size: 分页

函数 list_device_bookings(...):
query = db.query(DeviceBooking)

如果 q 非空:
like = "%" + q + "%"
query = query.filter(
device_name 像 like
或 user_name 像 like
或 student_id 像 like
或 reason 像 like
或 serial_number 像 like
)

如果 status 参数有值:
query = query.filter(DeviceBooking.status == status)

如果 date_from 有值:
query = query.filter(start_date >= date_from)
如果 date_to 有值:
query = query.filter(start_date <= date_to)

total = query.count()
rows = query.order_by(id.desc()).offset(...).limit(...).all()

返回 {
data: [ 每条记录转成字典,字段名用前端习惯的驼峰或下划线,与 appointments 保持一致 ],
pagination: {...}
}

列表每项建议包含:

id, device_id, device_name, serial_number, quantity, start_date, end_date, reason, student_id, name, class_name, major, phone, teacher_id, teacher_name, status, admin_remark, reject_reason, created_at

5.2 实现 PATCH /device-bookings/{id}/review(审核)

伪代码(与 review_appointment 几乎相同):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
类 DeviceBookingReviewBody:
action: 字符串,"approve" 或 "reject"
adminRemark: 字符串,默认空
rejectReason: 字符串,默认空

PATCH /device-bookings/{appointment_id}/review

row = 按 id 查 DeviceBooking,不存在 → 404

action = body.action 转小写

如果 action == "approve":
row.status = 1
row.admin_remark = body.adminRemark.strip()
row.reject_reason = ""
否则如果 action == "reject":
如果 body.rejectReason 为空 → 400 "拒绝时必须填写拒绝原因"
row.status = 2
row.reject_reason = body.rejectReason.strip()
否则:
400 "action 仅支持 approve/reject"

db.commit()
返回 { ok: true, data: { id, status, admin_remark, reject_reason } }

5.3 实现 PATCH /device-bookings/{id}/cancel(用户撤回)

伪代码:

1
2
3
4
5
6
7
8
9
10
PATCH /device-bookings/{id}/cancel

row = 按 id 查询,不存在 → 404

如果 row.status != 0:
400 "仅待审核状态可撤回"

row.status = 3
db.commit()
返回 { ok: true, data: { id, status: 3 } }

5.4 状态码速查表(贴在桌面)

status 含义 用户端文案
0 待审核 待审批
1 已通过 已通过
2 已拒绝 已拒绝
3 已取消 已取消

5.5 本章自测

步骤 操作 期望
5a GET /api/device-bookings 能看到第 4 章提交的记录
5b PATCH review action=approve status 变 1
5c 再提交一条,PATCH reject 不带原因 400
5d PATCH reject 带原因 status=2,reject_reason 有值
5e 再提交一条,PATCH cancel status=3

第 6 章 注册路由与完整流程自测

6.1 确认 main.py 注册了两个路由

伪代码:

1
2
3
4
from app.api.routes import devices, device_bookings

app.include_router(devices.router, prefix=API_PREFIX)
app.include_router(device_bookings.router, prefix=API_PREFIX)

保存后重启后端,打开 /docs 应能看到 devices 和 device-bookings 两组接口。

6.2 完整业务剧本(端到端)

按此顺序在 Swagger 中走一遍,全部通过即后端 P0 完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
【设备录入 — 管理端数据源头】
1. POST /api/devices
录入:名称="测试笔记本", serialNumber="LAP-TEST-001", category=0,
totalQuantity=3, availableQuantity=3, status=0
→ 记下返回的 id

2. POST /api/devices
再录入一台 category=2 的实验器材(可选,测筛选)

3. GET /api/devices?scope=admin
→ 确认两台都在

【用户浏览 — 公开列表】
4. GET /api/devices
→ 能看到 status=0 的设备,code 字段等于 serialNumber

5. GET /api/devices?category=0
→ 只有笔记本

【用户申请】
6. POST /api/device-bookings
deviceId=步骤1的id, name="测试学生", phone="138...", reason="课程实验",
borrowStart=今天, borrowEnd=七天后
→ 返回 ok, id

7. GET /api/device-bookings
→ 有一条 status=0

【管理员审核】
8. PATCH /api/device-bookings/{id}/review
{ "action": "approve", "adminRemark": "同意" }
→ status=1

9. 再 POST 一条申请 → PATCH reject(带 rejectReason)→ 确认 status=2

10. 再 POST 一条申请 → PATCH cancel → 确认 status=3

【库存与下架】
11. PUT /api/devices/{id} 设 availableQuantity=0
12. POST 新申请该设备 → 应 400 库存不足

13. PUT /api/devices/{id} 设 status=2(报废)
14. GET /api/devices(public)→ 不应再展示该设备

6.3 最终验收对照 PRD

验收编号 怎么证明完成了
AC-00 步骤 1~3:管理端可录入
AC-01 步骤 4:用户端列表来自 API
AC-02 步骤 6:申请落库
AC-05/06 步骤 8~9:审核
AC-07 步骤 10:撤回
AC-08 步骤 11~12:不可借
AC-10 步骤 13~14:下架后不可见
AC-11 重复 serialNumber POST 失败

第 7 章 交给 AI 的前端对接说明(先不需要手写,让TRAE来实现前端)

后端完成后,将以下内容交给 AI 实现前端(用户端 + 管理端)。

7.1 用户端需改的文件

文件 改动
frontend/src/api/index.js 新增 getDevices, submitDeviceBooking, getDeviceBookings, cancelDeviceBooking
frontend/src/views/EquipmentView.vue 删除 Mock;列表调 getDevices;提交调 submitDeviceBooking;我的申请调 getDeviceBookings;撤回绑 cancelDeviceBooking

7.2 管理端需改的文件

文件 改动
admin/src/router/index.js devices → AdminDevicesView;device-bookings → AdminDeviceBookingsView
新建 admin/src/views/AdminDevicesView.vue 设备列表 + 新增/编辑表单,调 devices CRUD API
新建 admin/src/views/AdminDeviceBookingsView.vue 参照 AdminAppointmentsView,调 device-bookings API

7.3 接口契约摘要(给 AI)

设备台账:

方法 路径 说明
GET /api/devices?category=&q=&page=&page_size= 用户端公开列表
GET /api/devices?scope=admin 管理端全量列表
GET /api/devices/{id} 详情
POST /api/devices 管理端录入
PUT /api/devices/{id} 管理端编辑

设备申请:

方法 路径 说明
GET /api/device-bookings 申请列表
POST /api/device-bookings 提交申请
PATCH /api/device-bookings/{id}/review 审核
PATCH /api/device-bookings/{id}/cancel 撤回

类别映射: 0 电脑 / 1 手机 / 2 实验器材 / 3 软件 → UI 四个 Tab。

设备可借: 响应 status 为 "available" 时可点击申请。

附录 A 概念速查

概念 在本项目里干什么
APIRouter 把一组 URL 接口组织在一个文件里
Depends(get_db) 每个请求自动拿一个数据库连接,用完关闭
BaseModel 定义请求/响应 JSON 的格式和校验规则
db.add(row) 把新对象放入待提交队列
db.commit() 真正写入数据库
db.refresh(row) 提交后刷新对象(拿到数据库生成的 id 等)
HTTPException(400, detail="...") 告诉前端请求不合法

附录 B 常见错误排查表

现象 排查顺序
Swagger 看不到新接口
main.py 是否 import 并 include_router;是否重启后端
404 Not Found URL 前缀是否为 /api/devices;router 的 prefix 是否写对
422 Unprocessable 请求 JSON 字段名是否与 Pydantic 一致(驼峰 vs 下划线)
500 Internal Server Error 看终端完整报错;多半是缺列或日期格式转换失败
列表为空 是否先用 POST 录入了设备;public scope 是否过滤掉了维修/报废
申请成功但列表看不到 GET 列表的排序/分页;是否查错表