初次提交项目
This commit is contained in:
194
app.py
Normal file
194
app.py
Normal file
@@ -0,0 +1,194 @@
|
||||
import os
|
||||
|
||||
from flask import Flask, redirect, render_template, request, url_for
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
DB_DIR = os.path.join(BASE_DIR, "data")
|
||||
os.makedirs(DB_DIR, exist_ok=True)
|
||||
DB_PATH = os.path.join(DB_DIR, "inventory.db")
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{DB_PATH}"
|
||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
|
||||
class Box(db.Model):
|
||||
__tablename__ = "boxes"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False, unique=True)
|
||||
description = db.Column(db.String(255), nullable=True)
|
||||
|
||||
|
||||
class Component(db.Model):
|
||||
__tablename__ = "components"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
box_id = db.Column(db.Integer, db.ForeignKey("boxes.id"), nullable=False)
|
||||
slot_index = db.Column(db.Integer, nullable=False)
|
||||
|
||||
part_no = db.Column(db.String(100), nullable=False)
|
||||
name = db.Column(db.String(120), nullable=False)
|
||||
specification = db.Column(db.String(120), nullable=True)
|
||||
quantity = db.Column(db.Integer, nullable=False, default=0)
|
||||
location = db.Column(db.String(120), nullable=True)
|
||||
note = db.Column(db.Text, nullable=True)
|
||||
|
||||
box = db.relationship("Box", backref=db.backref("components", lazy=True))
|
||||
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint("box_id", "slot_index", name="uq_box_slot"),
|
||||
)
|
||||
|
||||
|
||||
def ensure_default_box() -> None:
|
||||
if not Box.query.first():
|
||||
default_box = Box(name="默认大盒", description="每盒 28 个格子")
|
||||
db.session.add(default_box)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def slot_data_for_box(box_id: int):
|
||||
components = Component.query.filter_by(box_id=box_id).all()
|
||||
slot_map = {c.slot_index: c for c in components}
|
||||
slots = []
|
||||
for slot in range(1, 29):
|
||||
slots.append({"slot": slot, "component": slot_map.get(slot)})
|
||||
return slots
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
boxes = Box.query.order_by(Box.id.asc()).all()
|
||||
box_cards = []
|
||||
for box in boxes:
|
||||
used_count = Component.query.filter_by(box_id=box.id).count()
|
||||
box_cards.append({"box": box, "used_count": used_count})
|
||||
return render_template("index.html", box_cards=box_cards)
|
||||
|
||||
|
||||
@app.route("/box/<int:box_id>")
|
||||
def view_box(box_id: int):
|
||||
box = Box.query.get_or_404(box_id)
|
||||
slots = slot_data_for_box(box.id)
|
||||
return render_template("box.html", box=box, slots=slots)
|
||||
|
||||
|
||||
@app.route("/edit/<int:box_id>/<int:slot>", methods=["GET", "POST"])
|
||||
def edit_component(box_id: int, slot: int):
|
||||
if slot < 1 or slot > 28:
|
||||
return "无效的格子编号", 400
|
||||
|
||||
box = Box.query.get_or_404(box_id)
|
||||
component = Component.query.filter_by(box_id=box.id, slot_index=slot).first()
|
||||
|
||||
if request.method == "POST":
|
||||
action = request.form.get("action", "save")
|
||||
|
||||
if action == "delete":
|
||||
if component:
|
||||
db.session.delete(component)
|
||||
db.session.commit()
|
||||
return redirect(url_for("view_box", box_id=box.id))
|
||||
|
||||
part_no = request.form.get("part_no", "").strip()
|
||||
name = request.form.get("name", "").strip()
|
||||
specification = request.form.get("specification", "").strip()
|
||||
quantity_raw = request.form.get("quantity", "0").strip()
|
||||
location = request.form.get("location", "").strip()
|
||||
note = request.form.get("note", "").strip()
|
||||
|
||||
if not part_no or not name:
|
||||
error = "料号和名称不能为空"
|
||||
return render_template(
|
||||
"edit.html",
|
||||
box=box,
|
||||
slot=slot,
|
||||
component=component,
|
||||
error=error,
|
||||
)
|
||||
|
||||
try:
|
||||
quantity = int(quantity_raw)
|
||||
if quantity < 0:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
error = "数量必须是大于等于 0 的整数"
|
||||
return render_template(
|
||||
"edit.html",
|
||||
box=box,
|
||||
slot=slot,
|
||||
component=component,
|
||||
error=error,
|
||||
)
|
||||
|
||||
if component is None:
|
||||
component = Component(box_id=box.id, slot_index=slot)
|
||||
db.session.add(component)
|
||||
|
||||
component.part_no = part_no
|
||||
component.name = name
|
||||
component.specification = specification or None
|
||||
component.quantity = quantity
|
||||
component.location = location or None
|
||||
component.note = note or None
|
||||
|
||||
db.session.commit()
|
||||
return redirect(url_for("view_box", box_id=box.id))
|
||||
|
||||
return render_template("edit.html", box=box, slot=slot, component=component)
|
||||
|
||||
|
||||
@app.route("/scan")
|
||||
def scan_page():
|
||||
keyword = request.args.get("q", "").strip()
|
||||
results = []
|
||||
if keyword:
|
||||
results = (
|
||||
Component.query.filter(
|
||||
db.or_(
|
||||
Component.part_no.ilike(f"%{keyword}%"),
|
||||
Component.name.ilike(f"%{keyword}%"),
|
||||
)
|
||||
)
|
||||
.order_by(Component.part_no.asc())
|
||||
.all()
|
||||
)
|
||||
return render_template("scan.html", keyword=keyword, results=results)
|
||||
|
||||
|
||||
@app.route("/api/scan")
|
||||
def scan_api():
|
||||
code = request.args.get("code", "").strip()
|
||||
if not code:
|
||||
return {"ok": False, "message": "code 不能为空"}, 400
|
||||
|
||||
item = Component.query.filter_by(part_no=code).first()
|
||||
if not item:
|
||||
return {"ok": False, "message": "未找到元件"}, 404
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"data": {
|
||||
"id": item.id,
|
||||
"part_no": item.part_no,
|
||||
"name": item.name,
|
||||
"quantity": item.quantity,
|
||||
"box_id": item.box_id,
|
||||
"slot_index": item.slot_index,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def bootstrap() -> None:
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
ensure_default_box()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bootstrap()
|
||||
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||
Reference in New Issue
Block a user