feat:增强箱子管理功能与界面优化
- 新增箱子重命名与删除功能 - 引入新箱子前缀与起始编号配置 - 在首页展示箱子编号范围 - 添加概览按钮,快速查看已启用的物品及其名称 - 实现组件的启用/禁用功能 - 更新数据库结构,新增箱子与组件字段 - 优化箱子与组件管理界面,改进表单与表格展示 - 在索引页面增加箱子详细信息概览区域 - 增强扫描与搜索功能,优化结果显示效果
This commit is contained in:
59
README.md
59
README.md
@@ -8,6 +8,14 @@
|
|||||||
- `14格中盒大盒`:中等盒子,无固定摆放图。
|
- `14格中盒大盒`:中等盒子,无固定摆放图。
|
||||||
- `袋装清单`:防静电袋列表模式(每行一个袋位,支持批量新增)。
|
- `袋装清单`:防静电袋列表模式(每行一个袋位,支持批量新增)。
|
||||||
|
|
||||||
|
v1.1 新增能力:
|
||||||
|
|
||||||
|
- 支持盒子改名和删除。
|
||||||
|
- 新增盒子时可设置 `前缀 + 起始序号`,内部编号自动递增。
|
||||||
|
- 首页可直接看到每个盒子的编号范围(如 `A1-A28`)。
|
||||||
|
- 首页新增概览按钮:快速查看已启用的编号与名称。
|
||||||
|
- 编辑页支持 `启用/停用`。
|
||||||
|
|
||||||
## 1. 项目结构
|
## 1. 项目结构
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -59,15 +67,19 @@ python app.py
|
|||||||
|
|
||||||
- 按容器类型显示 3 个列表。
|
- 按容器类型显示 3 个列表。
|
||||||
- 每个列表都可新增盒子。
|
- 每个列表都可新增盒子。
|
||||||
|
- 每个盒子可在首页直接改名、修改前缀和起始序号、删除。
|
||||||
|
- 每个盒子有概览按钮,快速查看已启用条目。
|
||||||
|
|
||||||
### 3.2 盒子详情 `/box/<box_id>`
|
### 3.2 盒子详情 `/box/<box_id>`
|
||||||
|
|
||||||
- `28格/14格`:格子视图,点格子进入编辑。
|
- `28格/14格`:格子视图,点格子进入编辑。
|
||||||
- `袋装清单`:表格视图,支持单条新增和批量新增。
|
- `袋装清单`:表格视图,支持单条新增和批量新增。
|
||||||
|
- 页面显示自动编号范围(由前缀+起始序号生成)。
|
||||||
|
|
||||||
### 3.3 编辑页 `/edit/<box_id>/<slot>`
|
### 3.3 编辑页 `/edit/<box_id>/<slot>`
|
||||||
|
|
||||||
- 编辑料号、名称、规格、数量、位置备注、备注。
|
- 编辑料号、名称、规格、数量、位置备注、备注。
|
||||||
|
- 支持勾选启用,或通过按钮启用/停用。
|
||||||
- 可删除当前格子记录。
|
- 可删除当前格子记录。
|
||||||
|
|
||||||
### 3.4 扫码/搜索 `/scan`
|
### 3.4 扫码/搜索 `/scan`
|
||||||
@@ -96,13 +108,50 @@ python app.py
|
|||||||
- `数量` 需为大于等于 0 的整数(留空按 0)。
|
- `数量` 需为大于等于 0 的整数(留空按 0)。
|
||||||
- 无效行会跳过并提示。
|
- 无效行会跳过并提示。
|
||||||
|
|
||||||
## 5. 数据库说明
|
## 5. 自动编号规则(新增)
|
||||||
|
|
||||||
|
新增盒子时只需填写:
|
||||||
|
|
||||||
|
- `前缀`:如 `A`、`B`、`C`、`BAG`
|
||||||
|
- `起始序号`:如 `1`
|
||||||
|
|
||||||
|
系统自动生成内部编号:
|
||||||
|
|
||||||
|
- 第 1 位:`前缀 + 起始序号`
|
||||||
|
- 第 N 位:`前缀 + (起始序号 + N - 1)`
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
- 前缀 `A`、起始 `1`、容量 28 -> `A1-A28`
|
||||||
|
- 前缀 `B`、起始 `100`、容量 14 -> `B100-B113`
|
||||||
|
|
||||||
|
## 6. 元器件命名建议(简洁版)
|
||||||
|
|
||||||
|
为避免命名过长又保证可检索,建议:
|
||||||
|
|
||||||
|
- `料号(part_no)`:优先写厂家/采购料号,保持唯一。
|
||||||
|
- `名称(name)`:`品类 + 关键值 + 封装`,如 `电阻10K 0603`、`电容100nF 0603`。
|
||||||
|
- `规格(specification)`:补充精度/耐压/温漂等必要信息,如 `1%`、`50V X7R`。
|
||||||
|
|
||||||
|
推荐格式:
|
||||||
|
|
||||||
|
```text
|
||||||
|
名称: 电阻10K 0603
|
||||||
|
规格: 1%
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
名称: 电容100nF 0603
|
||||||
|
规格: 50V X7R
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 数据库说明
|
||||||
|
|
||||||
- 使用 SQLite,文件路径:`data/inventory.db`
|
- 使用 SQLite,文件路径:`data/inventory.db`
|
||||||
- 首次发布执行一次 `python init_db.py`
|
- 首次发布执行一次 `python init_db.py`
|
||||||
- 后续通常不需要重复初始化
|
- 后续通常不需要重复初始化
|
||||||
|
|
||||||
## 6. 服务器部署(宝塔)
|
## 8. 服务器部署(宝塔)
|
||||||
|
|
||||||
建议流程:
|
建议流程:
|
||||||
|
|
||||||
@@ -116,7 +165,7 @@ python app.py
|
|||||||
|
|
||||||
建议 Gunicorn 仅监听内网:`127.0.0.1:5000`
|
建议 Gunicorn 仅监听内网:`127.0.0.1:5000`
|
||||||
|
|
||||||
## 7. 日常发布流程
|
## 9. 日常发布流程
|
||||||
|
|
||||||
本地开发后:
|
本地开发后:
|
||||||
|
|
||||||
@@ -137,7 +186,7 @@ pip install -r requirements.txt
|
|||||||
|
|
||||||
最后在宝塔里重启 Python 项目。
|
最后在宝塔里重启 Python 项目。
|
||||||
|
|
||||||
## 8. 备份建议
|
## 10. 备份建议
|
||||||
|
|
||||||
重点备份:`data/inventory.db`
|
重点备份:`data/inventory.db`
|
||||||
|
|
||||||
@@ -147,7 +196,7 @@ pip install -r requirements.txt
|
|||||||
cp /www/wwwroot/inventory/data/inventory.db /www/backup/inventory_$(date +%F).db
|
cp /www/wwwroot/inventory/data/inventory.db /www/backup/inventory_$(date +%F).db
|
||||||
```
|
```
|
||||||
|
|
||||||
## 9. 常见问题
|
## 11. 常见问题
|
||||||
|
|
||||||
### Q1: VS Code 提示无法解析 `flask` 导入
|
### Q1: VS Code 提示无法解析 `flask` 导入
|
||||||
|
|
||||||
|
|||||||
377
app.py
377
app.py
@@ -21,16 +21,19 @@ BOX_TYPES = {
|
|||||||
"label": "28格小盒大盒",
|
"label": "28格小盒大盒",
|
||||||
"default_capacity": 28,
|
"default_capacity": 28,
|
||||||
"default_desc": "4连排小盒,常见摆放为竖向7排",
|
"default_desc": "4连排小盒,常见摆放为竖向7排",
|
||||||
|
"default_prefix": "A",
|
||||||
},
|
},
|
||||||
"medium_14": {
|
"medium_14": {
|
||||||
"label": "14格中盒大盒",
|
"label": "14格中盒大盒",
|
||||||
"default_capacity": 14,
|
"default_capacity": 14,
|
||||||
"default_desc": "装十四个中等盒子(尺寸不固定)",
|
"default_desc": "装十四个中等盒子(尺寸不固定)",
|
||||||
|
"default_prefix": "B",
|
||||||
},
|
},
|
||||||
"bag": {
|
"bag": {
|
||||||
"label": "袋装清单",
|
"label": "袋装清单",
|
||||||
"default_capacity": 1,
|
"default_capacity": 1,
|
||||||
"default_desc": "用于较小防静电袋存放",
|
"default_desc": "用于较小防静电袋存放",
|
||||||
|
"default_prefix": "BAG",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +46,8 @@ class Box(db.Model):
|
|||||||
description = db.Column(db.String(255), nullable=True)
|
description = db.Column(db.String(255), nullable=True)
|
||||||
box_type = db.Column(db.String(30), nullable=False, default="small_28")
|
box_type = db.Column(db.String(30), nullable=False, default="small_28")
|
||||||
slot_capacity = db.Column(db.Integer, nullable=False, default=28)
|
slot_capacity = db.Column(db.Integer, nullable=False, default=28)
|
||||||
|
slot_prefix = db.Column(db.String(16), nullable=False, default="A")
|
||||||
|
start_number = db.Column(db.Integer, nullable=False, default=1)
|
||||||
|
|
||||||
|
|
||||||
class Component(db.Model):
|
class Component(db.Model):
|
||||||
@@ -58,6 +63,7 @@ class Component(db.Model):
|
|||||||
quantity = db.Column(db.Integer, nullable=False, default=0)
|
quantity = db.Column(db.Integer, nullable=False, default=0)
|
||||||
location = db.Column(db.String(120), nullable=True)
|
location = db.Column(db.String(120), nullable=True)
|
||||||
note = db.Column(db.Text, nullable=True)
|
note = db.Column(db.Text, nullable=True)
|
||||||
|
is_enabled = db.Column(db.Boolean, nullable=False, default=True)
|
||||||
|
|
||||||
box = db.relationship("Box", backref=db.backref("components", lazy=True))
|
box = db.relationship("Box", backref=db.backref("components", lazy=True))
|
||||||
|
|
||||||
@@ -66,43 +72,80 @@ class Component(db.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def ensure_default_box() -> None:
|
def _add_column_if_missing(table_name: str, column_name: str, ddl: str) -> None:
|
||||||
defaults = [
|
columns = {
|
||||||
("默认28格大盒", "small_28"),
|
row[1]
|
||||||
("默认14格中盒", "medium_14"),
|
for row in db.session.execute(db.text(f"PRAGMA table_info({table_name})")).fetchall()
|
||||||
("默认袋装清单", "bag"),
|
}
|
||||||
]
|
if column_name not in columns:
|
||||||
for box_name, box_type in defaults:
|
db.session.execute(db.text(f"ALTER TABLE {table_name} ADD COLUMN {ddl}"))
|
||||||
exists = Box.query.filter_by(name=box_name).first()
|
|
||||||
if exists:
|
|
||||||
continue
|
def ensure_schema() -> None:
|
||||||
meta = BOX_TYPES[box_type]
|
_add_column_if_missing(
|
||||||
db.session.add(
|
"boxes",
|
||||||
Box(
|
"box_type",
|
||||||
name=box_name,
|
"box_type VARCHAR(30) NOT NULL DEFAULT 'small_28'",
|
||||||
description=meta["default_desc"],
|
|
||||||
box_type=box_type,
|
|
||||||
slot_capacity=meta["default_capacity"],
|
|
||||||
)
|
)
|
||||||
|
_add_column_if_missing(
|
||||||
|
"boxes",
|
||||||
|
"slot_capacity",
|
||||||
|
"slot_capacity INTEGER NOT NULL DEFAULT 28",
|
||||||
|
)
|
||||||
|
_add_column_if_missing(
|
||||||
|
"boxes",
|
||||||
|
"slot_prefix",
|
||||||
|
"slot_prefix VARCHAR(16) NOT NULL DEFAULT 'A'",
|
||||||
|
)
|
||||||
|
_add_column_if_missing(
|
||||||
|
"boxes",
|
||||||
|
"start_number",
|
||||||
|
"start_number INTEGER NOT NULL DEFAULT 1",
|
||||||
|
)
|
||||||
|
_add_column_if_missing(
|
||||||
|
"components",
|
||||||
|
"is_enabled",
|
||||||
|
"is_enabled BOOLEAN NOT NULL DEFAULT 1",
|
||||||
)
|
)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def slot_code_for_box(box: Box, slot_index: int) -> str:
|
||||||
|
serial = box.start_number + slot_index - 1
|
||||||
|
return f"{box.slot_prefix}{serial}"
|
||||||
|
|
||||||
|
|
||||||
|
def slot_range_label(box: Box) -> str:
|
||||||
|
start_code = slot_code_for_box(box, 1)
|
||||||
|
end_code = slot_code_for_box(box, box.slot_capacity)
|
||||||
|
return f"{start_code}-{end_code}"
|
||||||
|
|
||||||
|
|
||||||
def slot_data_for_box(box: Box):
|
def slot_data_for_box(box: Box):
|
||||||
components = Component.query.filter_by(box_id=box.id).all()
|
components = Component.query.filter_by(box_id=box.id).all()
|
||||||
slot_map = {c.slot_index: c for c in components}
|
slot_map = {c.slot_index: c for c in components}
|
||||||
slots = []
|
slots = []
|
||||||
for slot in range(1, box.slot_capacity + 1):
|
for slot in range(1, box.slot_capacity + 1):
|
||||||
slots.append({"slot": slot, "component": slot_map.get(slot)})
|
slots.append(
|
||||||
|
{
|
||||||
|
"slot": slot,
|
||||||
|
"slot_code": slot_code_for_box(box, slot),
|
||||||
|
"component": slot_map.get(slot),
|
||||||
|
}
|
||||||
|
)
|
||||||
return slots
|
return slots
|
||||||
|
|
||||||
|
|
||||||
def bag_items_for_box(box: Box):
|
def bag_rows_for_box(box: Box):
|
||||||
return (
|
rows = []
|
||||||
|
components = (
|
||||||
Component.query.filter_by(box_id=box.id)
|
Component.query.filter_by(box_id=box.id)
|
||||||
.order_by(Component.slot_index.asc())
|
.order_by(Component.slot_index.asc())
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
for c in components:
|
||||||
|
rows.append({"component": c, "slot_code": slot_code_for_box(box, c.slot_index)})
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
def _parse_non_negative_int(raw_value: str, default_value: int = 0) -> int:
|
def _parse_non_negative_int(raw_value: str, default_value: int = 0) -> int:
|
||||||
@@ -115,80 +158,114 @@ def _parse_non_negative_int(raw_value: str, default_value: int = 0) -> int:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _add_bag_item(
|
def ensure_default_boxes() -> None:
|
||||||
box: Box,
|
defaults = [
|
||||||
part_no: str,
|
("默认28格大盒", "small_28"),
|
||||||
name: str,
|
("默认14格中盒", "medium_14"),
|
||||||
quantity: int,
|
("默认袋装清单", "bag"),
|
||||||
specification: str,
|
]
|
||||||
location: str,
|
changed = False
|
||||||
note: str,
|
for box_name, box_type in defaults:
|
||||||
) -> None:
|
if Box.query.filter_by(name=box_name).first():
|
||||||
next_slot = (
|
continue
|
||||||
db.session.query(db.func.max(Component.slot_index))
|
meta = BOX_TYPES[box_type]
|
||||||
.filter(Component.box_id == box.id)
|
db.session.add(
|
||||||
.scalar()
|
Box(
|
||||||
or 0
|
name=box_name,
|
||||||
) + 1
|
description=meta["default_desc"],
|
||||||
|
box_type=box_type,
|
||||||
item = Component(
|
slot_capacity=meta["default_capacity"],
|
||||||
box_id=box.id,
|
slot_prefix=meta["default_prefix"],
|
||||||
slot_index=next_slot,
|
start_number=1,
|
||||||
part_no=part_no,
|
|
||||||
name=name,
|
|
||||||
quantity=quantity,
|
|
||||||
specification=specification or None,
|
|
||||||
location=location or None,
|
|
||||||
note=note or None,
|
|
||||||
)
|
)
|
||||||
db.session.add(item)
|
)
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
if next_slot > box.slot_capacity:
|
|
||||||
box.slot_capacity = next_slot
|
def normalize_legacy_data() -> None:
|
||||||
|
db.session.execute(
|
||||||
|
db.text(
|
||||||
|
"UPDATE boxes SET box_type = 'small_28' WHERE box_type IS NULL OR box_type = ''"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
db.session.execute(
|
||||||
|
db.text("UPDATE boxes SET slot_capacity = 28 WHERE slot_capacity IS NULL")
|
||||||
|
)
|
||||||
|
db.session.execute(
|
||||||
|
db.text("UPDATE boxes SET slot_prefix = 'A' WHERE slot_prefix IS NULL OR slot_prefix = ''")
|
||||||
|
)
|
||||||
|
db.session.execute(
|
||||||
|
db.text("UPDATE boxes SET start_number = 1 WHERE start_number IS NULL")
|
||||||
|
)
|
||||||
|
db.session.execute(
|
||||||
|
db.text("UPDATE components SET is_enabled = 1 WHERE is_enabled IS NULL")
|
||||||
|
)
|
||||||
|
|
||||||
|
for box in Box.query.all():
|
||||||
|
if box.box_type not in BOX_TYPES:
|
||||||
|
box.box_type = "small_28"
|
||||||
|
if not box.slot_capacity or box.slot_capacity < 1:
|
||||||
|
box.slot_capacity = BOX_TYPES[box.box_type]["default_capacity"]
|
||||||
|
if not box.slot_prefix:
|
||||||
|
box.slot_prefix = BOX_TYPES[box.box_type]["default_prefix"]
|
||||||
|
if box.start_number is None or box.start_number < 0:
|
||||||
|
box.start_number = 1
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def make_overview_rows(box: Box):
|
||||||
|
enabled_components = (
|
||||||
|
Component.query.filter_by(box_id=box.id, is_enabled=True)
|
||||||
|
.order_by(Component.slot_index.asc())
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
rows = []
|
||||||
|
for c in enabled_components:
|
||||||
|
rows.append(
|
||||||
|
{
|
||||||
|
"slot_code": slot_code_for_box(box, c.slot_index),
|
||||||
|
"name": c.name,
|
||||||
|
"part_no": c.part_no,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
def render_box_page(box: Box, error: str = "", notice: str = ""):
|
def render_box_page(box: Box, error: str = "", notice: str = ""):
|
||||||
slots = slot_data_for_box(box)
|
slots = slot_data_for_box(box)
|
||||||
bag_items = bag_items_for_box(box) if box.box_type == "bag" else []
|
bag_rows = bag_rows_for_box(box) if box.box_type == "bag" else []
|
||||||
return render_template(
|
return render_template(
|
||||||
"box.html",
|
"box.html",
|
||||||
box=box,
|
box=box,
|
||||||
slots=slots,
|
slots=slots,
|
||||||
bag_items=bag_items,
|
bag_rows=bag_rows,
|
||||||
box_types=BOX_TYPES,
|
box_types=BOX_TYPES,
|
||||||
|
slot_range=slot_range_label(box),
|
||||||
error=error,
|
error=error,
|
||||||
notice=notice,
|
notice=notice,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def ensure_box_schema() -> None:
|
|
||||||
columns = {
|
|
||||||
row[1]
|
|
||||||
for row in db.session.execute(db.text("PRAGMA table_info(boxes)")).fetchall()
|
|
||||||
}
|
|
||||||
if "box_type" not in columns:
|
|
||||||
db.session.execute(
|
|
||||||
db.text(
|
|
||||||
"ALTER TABLE boxes ADD COLUMN box_type VARCHAR(30) NOT NULL DEFAULT 'small_28'"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if "slot_capacity" not in columns:
|
|
||||||
db.session.execute(
|
|
||||||
db.text(
|
|
||||||
"ALTER TABLE boxes ADD COLUMN slot_capacity INTEGER NOT NULL DEFAULT 28"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
boxes = Box.query.order_by(Box.id.asc()).all()
|
boxes = Box.query.order_by(Box.id.asc()).all()
|
||||||
groups = {key: [] for key in BOX_TYPES.keys()}
|
groups = {key: [] for key in BOX_TYPES.keys()}
|
||||||
|
|
||||||
for box in boxes:
|
for box in boxes:
|
||||||
used_count = Component.query.filter_by(box_id=box.id).count()
|
|
||||||
box_type = box.box_type if box.box_type in BOX_TYPES else "small_28"
|
box_type = box.box_type if box.box_type in BOX_TYPES else "small_28"
|
||||||
groups[box_type].append({"box": box, "used_count": used_count})
|
overview_rows = make_overview_rows(box)
|
||||||
|
groups[box_type].append(
|
||||||
|
{
|
||||||
|
"box": box,
|
||||||
|
"used_count": len(overview_rows),
|
||||||
|
"slot_range": slot_range_label(box),
|
||||||
|
"overview_rows": overview_rows,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return render_template("index.html", groups=groups, box_types=BOX_TYPES)
|
return render_template("index.html", groups=groups, box_types=BOX_TYPES)
|
||||||
|
|
||||||
|
|
||||||
@@ -197,27 +274,71 @@ def create_box():
|
|||||||
box_type = request.form.get("box_type", "small_28").strip()
|
box_type = request.form.get("box_type", "small_28").strip()
|
||||||
name = request.form.get("name", "").strip()
|
name = request.form.get("name", "").strip()
|
||||||
description = request.form.get("description", "").strip()
|
description = request.form.get("description", "").strip()
|
||||||
|
slot_prefix = request.form.get("slot_prefix", "").strip().upper()
|
||||||
|
|
||||||
if box_type not in BOX_TYPES:
|
if box_type not in BOX_TYPES:
|
||||||
return "无效盒子类型", 400
|
return "无效盒子类型", 400
|
||||||
if not name:
|
if not name:
|
||||||
return "盒子名称不能为空", 400
|
return "盒子名称不能为空", 400
|
||||||
|
|
||||||
if Box.query.filter_by(name=name).first():
|
if Box.query.filter_by(name=name).first():
|
||||||
return "盒子名称已存在,请更换", 400
|
return "盒子名称已存在,请更换", 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
start_number = _parse_non_negative_int(request.form.get("start_number", "1"), 1)
|
||||||
|
except ValueError:
|
||||||
|
return "起始序号必须是大于等于 0 的整数", 400
|
||||||
|
|
||||||
meta = BOX_TYPES[box_type]
|
meta = BOX_TYPES[box_type]
|
||||||
box = Box(
|
box = Box(
|
||||||
name=name,
|
name=name,
|
||||||
description=description or meta["default_desc"],
|
description=description or meta["default_desc"],
|
||||||
box_type=box_type,
|
box_type=box_type,
|
||||||
slot_capacity=meta["default_capacity"],
|
slot_capacity=meta["default_capacity"],
|
||||||
|
slot_prefix=slot_prefix or meta["default_prefix"],
|
||||||
|
start_number=start_number,
|
||||||
)
|
)
|
||||||
db.session.add(box)
|
db.session.add(box)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/boxes/<int:box_id>/update", methods=["POST"])
|
||||||
|
def update_box(box_id: int):
|
||||||
|
box = Box.query.get_or_404(box_id)
|
||||||
|
|
||||||
|
new_name = request.form.get("name", "").strip()
|
||||||
|
description = request.form.get("description", "").strip()
|
||||||
|
slot_prefix = request.form.get("slot_prefix", "").strip().upper()
|
||||||
|
|
||||||
|
if not new_name:
|
||||||
|
return "盒子名称不能为空", 400
|
||||||
|
|
||||||
|
duplicate = Box.query.filter(Box.name == new_name, Box.id != box.id).first()
|
||||||
|
if duplicate:
|
||||||
|
return "盒子名称已存在,请更换", 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
start_number = _parse_non_negative_int(request.form.get("start_number", "1"), 1)
|
||||||
|
except ValueError:
|
||||||
|
return "起始序号必须是大于等于 0 的整数", 400
|
||||||
|
|
||||||
|
box.name = new_name
|
||||||
|
box.description = description or BOX_TYPES[box.box_type]["default_desc"]
|
||||||
|
box.slot_prefix = slot_prefix or BOX_TYPES[box.box_type]["default_prefix"]
|
||||||
|
box.start_number = start_number
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/boxes/<int:box_id>/delete", methods=["POST"])
|
||||||
|
def delete_box(box_id: int):
|
||||||
|
box = Box.query.get_or_404(box_id)
|
||||||
|
Component.query.filter_by(box_id=box.id).delete()
|
||||||
|
db.session.delete(box)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/box/<int:box_id>")
|
@app.route("/box/<int:box_id>")
|
||||||
def view_box(box_id: int):
|
def view_box(box_id: int):
|
||||||
box = Box.query.get_or_404(box_id)
|
box = Box.query.get_or_404(box_id)
|
||||||
@@ -244,7 +365,29 @@ def add_bag_item(box_id: int):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return render_box_page(box, error="袋装新增失败: 数量必须是大于等于 0 的整数")
|
return render_box_page(box, error="袋装新增失败: 数量必须是大于等于 0 的整数")
|
||||||
|
|
||||||
_add_bag_item(box, part_no, name, quantity, specification, location, note)
|
next_slot = (
|
||||||
|
db.session.query(db.func.max(Component.slot_index))
|
||||||
|
.filter(Component.box_id == box.id)
|
||||||
|
.scalar()
|
||||||
|
or 0
|
||||||
|
) + 1
|
||||||
|
|
||||||
|
item = Component(
|
||||||
|
box_id=box.id,
|
||||||
|
slot_index=next_slot,
|
||||||
|
part_no=part_no,
|
||||||
|
name=name,
|
||||||
|
quantity=quantity,
|
||||||
|
specification=specification or None,
|
||||||
|
location=location or None,
|
||||||
|
note=note or None,
|
||||||
|
is_enabled=True,
|
||||||
|
)
|
||||||
|
db.session.add(item)
|
||||||
|
|
||||||
|
if next_slot > box.slot_capacity:
|
||||||
|
box.slot_capacity = next_slot
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return render_box_page(box, notice="已新增 1 条袋装记录")
|
return render_box_page(box, notice="已新增 1 条袋装记录")
|
||||||
|
|
||||||
@@ -262,6 +405,13 @@ def add_bag_items_batch(box_id: int):
|
|||||||
|
|
||||||
invalid_lines = []
|
invalid_lines = []
|
||||||
added_count = 0
|
added_count = 0
|
||||||
|
next_slot = (
|
||||||
|
db.session.query(db.func.max(Component.slot_index))
|
||||||
|
.filter(Component.box_id == box.id)
|
||||||
|
.scalar()
|
||||||
|
or 0
|
||||||
|
) + 1
|
||||||
|
|
||||||
for line_no, line in enumerate(lines, start=1):
|
for line_no, line in enumerate(lines, start=1):
|
||||||
parts = [p.strip() for p in re.split(r"[,\t]", line)]
|
parts = [p.strip() for p in re.split(r"[,\t]", line)]
|
||||||
while len(parts) < 6:
|
while len(parts) < 6:
|
||||||
@@ -284,10 +434,24 @@ def add_bag_items_batch(box_id: int):
|
|||||||
invalid_lines.append(f"第 {line_no} 行")
|
invalid_lines.append(f"第 {line_no} 行")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
_add_bag_item(box, part_no, name, quantity, specification, location, note)
|
db.session.add(
|
||||||
|
Component(
|
||||||
|
box_id=box.id,
|
||||||
|
slot_index=next_slot,
|
||||||
|
part_no=part_no,
|
||||||
|
name=name,
|
||||||
|
quantity=quantity,
|
||||||
|
specification=specification or None,
|
||||||
|
location=location or None,
|
||||||
|
note=note or None,
|
||||||
|
is_enabled=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
added_count += 1
|
added_count += 1
|
||||||
|
next_slot += 1
|
||||||
|
|
||||||
if added_count:
|
if added_count:
|
||||||
|
box.slot_capacity = max(box.slot_capacity, next_slot - 1)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
if invalid_lines and added_count == 0:
|
if invalid_lines and added_count == 0:
|
||||||
@@ -322,12 +486,25 @@ def edit_component(box_id: int, slot: int):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for("view_box", box_id=box.id))
|
return redirect(url_for("view_box", box_id=box.id))
|
||||||
|
|
||||||
|
if action == "toggle_enable":
|
||||||
|
if component:
|
||||||
|
component.is_enabled = True
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for("edit_component", box_id=box.id, slot=slot))
|
||||||
|
|
||||||
|
if action == "toggle_disable":
|
||||||
|
if component:
|
||||||
|
component.is_enabled = False
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for("edit_component", box_id=box.id, slot=slot))
|
||||||
|
|
||||||
part_no = request.form.get("part_no", "").strip()
|
part_no = request.form.get("part_no", "").strip()
|
||||||
name = request.form.get("name", "").strip()
|
name = request.form.get("name", "").strip()
|
||||||
specification = request.form.get("specification", "").strip()
|
specification = request.form.get("specification", "").strip()
|
||||||
quantity_raw = request.form.get("quantity", "0").strip()
|
quantity_raw = request.form.get("quantity", "0").strip()
|
||||||
location = request.form.get("location", "").strip()
|
location = request.form.get("location", "").strip()
|
||||||
note = request.form.get("note", "").strip()
|
note = request.form.get("note", "").strip()
|
||||||
|
is_enabled = request.form.get("is_enabled") == "1"
|
||||||
|
|
||||||
if not part_no or not name:
|
if not part_no or not name:
|
||||||
error = "料号和名称不能为空"
|
error = "料号和名称不能为空"
|
||||||
@@ -335,20 +512,20 @@ def edit_component(box_id: int, slot: int):
|
|||||||
"edit.html",
|
"edit.html",
|
||||||
box=box,
|
box=box,
|
||||||
slot=slot,
|
slot=slot,
|
||||||
|
slot_code=slot_code_for_box(box, slot),
|
||||||
component=component,
|
component=component,
|
||||||
error=error,
|
error=error,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
quantity = int(quantity_raw)
|
quantity = _parse_non_negative_int(quantity_raw, 0)
|
||||||
if quantity < 0:
|
|
||||||
raise ValueError
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
error = "数量必须是大于等于 0 的整数"
|
error = "数量必须是大于等于 0 的整数"
|
||||||
return render_template(
|
return render_template(
|
||||||
"edit.html",
|
"edit.html",
|
||||||
box=box,
|
box=box,
|
||||||
slot=slot,
|
slot=slot,
|
||||||
|
slot_code=slot_code_for_box(box, slot),
|
||||||
component=component,
|
component=component,
|
||||||
error=error,
|
error=error,
|
||||||
)
|
)
|
||||||
@@ -363,20 +540,29 @@ def edit_component(box_id: int, slot: int):
|
|||||||
component.quantity = quantity
|
component.quantity = quantity
|
||||||
component.location = location or None
|
component.location = location or None
|
||||||
component.note = note or None
|
component.note = note or None
|
||||||
|
component.is_enabled = is_enabled
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for("view_box", box_id=box.id))
|
return redirect(url_for("view_box", box_id=box.id))
|
||||||
|
|
||||||
return render_template("edit.html", box=box, slot=slot, component=component)
|
return render_template(
|
||||||
|
"edit.html",
|
||||||
|
box=box,
|
||||||
|
slot=slot,
|
||||||
|
slot_code=slot_code_for_box(box, slot),
|
||||||
|
component=component,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/scan")
|
@app.route("/scan")
|
||||||
def scan_page():
|
def scan_page():
|
||||||
keyword = request.args.get("q", "").strip()
|
keyword = request.args.get("q", "").strip()
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
if keyword:
|
if keyword:
|
||||||
results = (
|
raw_results = (
|
||||||
Component.query.filter(
|
Component.query.join(Box, Box.id == Component.box_id)
|
||||||
|
.filter(
|
||||||
db.or_(
|
db.or_(
|
||||||
Component.part_no.ilike(f"%{keyword}%"),
|
Component.part_no.ilike(f"%{keyword}%"),
|
||||||
Component.name.ilike(f"%{keyword}%"),
|
Component.name.ilike(f"%{keyword}%"),
|
||||||
@@ -385,6 +571,17 @@ def scan_page():
|
|||||||
.order_by(Component.part_no.asc())
|
.order_by(Component.part_no.asc())
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for c in raw_results:
|
||||||
|
box = Box.query.get(c.box_id)
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
"component": c,
|
||||||
|
"box_name": box.name if box else f"盒 {c.box_id}",
|
||||||
|
"slot_code": slot_code_for_box(box, c.slot_index) if box else str(c.slot_index),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return render_template("scan.html", keyword=keyword, results=results)
|
return render_template("scan.html", keyword=keyword, results=results)
|
||||||
|
|
||||||
|
|
||||||
@@ -398,6 +595,7 @@ def scan_api():
|
|||||||
if not item:
|
if not item:
|
||||||
return {"ok": False, "message": "未找到元件"}, 404
|
return {"ok": False, "message": "未找到元件"}, 404
|
||||||
|
|
||||||
|
box = Box.query.get(item.box_id)
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"data": {
|
"data": {
|
||||||
@@ -406,7 +604,10 @@ def scan_api():
|
|||||||
"name": item.name,
|
"name": item.name,
|
||||||
"quantity": item.quantity,
|
"quantity": item.quantity,
|
||||||
"box_id": item.box_id,
|
"box_id": item.box_id,
|
||||||
|
"box_name": box.name if box else None,
|
||||||
"slot_index": item.slot_index,
|
"slot_index": item.slot_index,
|
||||||
|
"slot_code": slot_code_for_box(box, item.slot_index) if box else str(item.slot_index),
|
||||||
|
"is_enabled": bool(item.is_enabled),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,17 +615,9 @@ def scan_api():
|
|||||||
def bootstrap() -> None:
|
def bootstrap() -> None:
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
ensure_box_schema()
|
ensure_schema()
|
||||||
db.session.execute(
|
normalize_legacy_data()
|
||||||
db.text(
|
ensure_default_boxes()
|
||||||
"UPDATE boxes SET box_type = 'small_28' WHERE box_type IS NULL OR box_type = ''"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
db.session.execute(
|
|
||||||
db.text("UPDATE boxes SET slot_capacity = 28 WHERE slot_capacity IS NULL")
|
|
||||||
)
|
|
||||||
db.session.commit()
|
|
||||||
ensure_default_box()
|
|
||||||
|
|
||||||
|
|
||||||
bootstrap()
|
bootstrap()
|
||||||
|
|||||||
@@ -100,11 +100,15 @@ body {
|
|||||||
|
|
||||||
.new-box-form {
|
.new-box-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.2fr 1fr auto;
|
grid-template-columns: 1.2fr 0.8fr 0.7fr 1fr auto;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.new-box-form.compact {
|
||||||
|
grid-template-columns: 1.1fr 0.8fr 0.7fr 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
.box-card,
|
.box-card,
|
||||||
.panel {
|
.panel {
|
||||||
background: var(--card);
|
background: var(--card);
|
||||||
@@ -122,6 +126,34 @@ body {
|
|||||||
margin: 0 0 6px;
|
margin: 0 0 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-actions form {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-overview {
|
||||||
|
margin-top: 8px;
|
||||||
|
border: 1px dashed var(--line);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-overview summary {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-overview ul {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
padding-left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.slot-grid {
|
.slot-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, minmax(90px, 1fr));
|
grid-template-columns: repeat(7, minmax(90px, 1fr));
|
||||||
|
|||||||
@@ -26,27 +26,30 @@
|
|||||||
{% if box.box_type == 'bag' %}
|
{% if box.box_type == 'bag' %}
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h2>袋装记录</h2>
|
<h2>袋装记录</h2>
|
||||||
<p class="group-desc">每行一个袋位,按序号自动编号</p>
|
<p class="group-desc">编号范围: {{ slot_range }} | 每行一个袋位</p>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>袋位</th>
|
<th>袋位编号</th>
|
||||||
<th>料号</th>
|
<th>料号</th>
|
||||||
<th>名称</th>
|
<th>名称</th>
|
||||||
<th>数量</th>
|
<th>数量</th>
|
||||||
|
<th>状态</th>
|
||||||
<th>规格</th>
|
<th>规格</th>
|
||||||
<th>位置备注</th>
|
<th>位置备注</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for c in bag_items %}
|
{% for row in bag_rows %}
|
||||||
|
{% set c = row.component %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>#{{ c.slot_index }}</td>
|
<td>{{ row.slot_code }}</td>
|
||||||
<td>{{ c.part_no }}</td>
|
<td>{{ c.part_no }}</td>
|
||||||
<td>{{ c.name }}</td>
|
<td>{{ c.name }}</td>
|
||||||
<td>{{ c.quantity }}</td>
|
<td>{{ c.quantity }}</td>
|
||||||
|
<td>{% if c.is_enabled %}启用{% else %}停用{% endif %}</td>
|
||||||
<td>{{ c.specification or '-' }}</td>
|
<td>{{ c.specification or '-' }}</td>
|
||||||
<td>{{ c.location or '-' }}</td>
|
<td>{{ c.location or '-' }}</td>
|
||||||
<td><a href="{{ url_for('edit_component', box_id=box.id, slot=c.slot_index) }}">编辑</a></td>
|
<td><a href="{{ url_for('edit_component', box_id=box.id, slot=c.slot_index) }}">编辑</a></td>
|
||||||
@@ -105,15 +108,15 @@
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="group-desc">容量: {{ box.slot_capacity }} 位</p>
|
<p class="group-desc">容量: {{ box.slot_capacity }} 位 | 编号范围: {{ slot_range }}</p>
|
||||||
<section class="slot-grid{% if box.slot_capacity <= 14 %} slot-grid-14{% endif %}{% if box.slot_capacity <= 4 %} slot-grid-bag{% endif %}">
|
<section class="slot-grid{% if box.slot_capacity <= 14 %} slot-grid-14{% endif %}{% if box.slot_capacity <= 4 %} slot-grid-bag{% endif %}">
|
||||||
{% for item in slots %}
|
{% for item in slots %}
|
||||||
<a class="slot {% if item.component %}filled{% endif %}" href="{{ url_for('edit_component', box_id=box.id, slot=item.slot) }}">
|
<a class="slot {% if item.component %}filled{% endif %}" href="{{ url_for('edit_component', box_id=box.id, slot=item.slot) }}">
|
||||||
<span class="slot-no">#{{ item.slot }}</span>
|
<span class="slot-no">{{ item.slot_code }}</span>
|
||||||
{% if item.component %}
|
{% if item.component %}
|
||||||
<strong>{{ item.component.part_no }}</strong>
|
<strong>{{ item.component.part_no }}</strong>
|
||||||
<small>{{ item.component.name }}</small>
|
<small>{{ item.component.name }}</small>
|
||||||
<small>库存: {{ item.component.quantity }}</small>
|
<small>库存: {{ item.component.quantity }} | {% if item.component.is_enabled %}启用{% else %}停用{% endif %}</small>
|
||||||
{% else %}
|
{% else %}
|
||||||
<small>空位</small>
|
<small>空位</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="hero slim">
|
<header class="hero slim">
|
||||||
<h1>{{ box.name }} - 格子 #{{ slot }}</h1>
|
<h1>{{ box.name }} - 编号 {{ slot_code }}</h1>
|
||||||
<a class="btn btn-light" href="{{ url_for('view_box', box_id=box.id) }}">返回宫格</a>
|
<a class="btn btn-light" href="{{ url_for('view_box', box_id=box.id) }}">返回宫格</a>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -38,6 +38,10 @@
|
|||||||
位置备注
|
位置备注
|
||||||
<input type="text" name="location" value="{{ component.location if component else '' }}">
|
<input type="text" name="location" value="{{ component.location if component else '' }}">
|
||||||
</label>
|
</label>
|
||||||
|
<label class="full">
|
||||||
|
<input type="checkbox" name="is_enabled" value="1" {% if component is none or component.is_enabled %}checked{% endif %}>
|
||||||
|
启用该位
|
||||||
|
</label>
|
||||||
<label class="full">
|
<label class="full">
|
||||||
备注
|
备注
|
||||||
<textarea name="note" rows="3">{{ component.note if component else '' }}</textarea>
|
<textarea name="note" rows="3">{{ component.note if component else '' }}</textarea>
|
||||||
@@ -46,6 +50,11 @@
|
|||||||
<div class="actions full">
|
<div class="actions full">
|
||||||
<button class="btn" type="submit" name="action" value="save">保存</button>
|
<button class="btn" type="submit" name="action" value="save">保存</button>
|
||||||
{% if component %}
|
{% if component %}
|
||||||
|
{% if component.is_enabled %}
|
||||||
|
<button class="btn btn-light" type="submit" name="action" value="toggle_disable">停用</button>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-light" type="submit" name="action" value="toggle_enable">启用</button>
|
||||||
|
{% endif %}
|
||||||
<button class="btn btn-danger" type="submit" name="action" value="delete" onclick="return confirm('确认清空该格子吗?')">删除</button>
|
<button class="btn btn-danger" type="submit" name="action" value="delete" onclick="return confirm('确认清空该格子吗?')">删除</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
<form class="new-box-form" method="post" action="{{ url_for('create_box') }}">
|
<form class="new-box-form" method="post" action="{{ url_for('create_box') }}">
|
||||||
<input type="hidden" name="box_type" value="{{ key }}">
|
<input type="hidden" name="box_type" value="{{ key }}">
|
||||||
<input type="text" name="name" placeholder="新增盒子名称" required>
|
<input type="text" name="name" placeholder="新增盒子名称" required>
|
||||||
|
<input type="text" name="slot_prefix" placeholder="前缀(如A/B/C)">
|
||||||
|
<input type="number" name="start_number" min="0" value="1" placeholder="起始序号">
|
||||||
<input type="text" name="description" placeholder="备注(可选)">
|
<input type="text" name="description" placeholder="备注(可选)">
|
||||||
<button class="btn" type="submit">新增盒子</button>
|
<button class="btn" type="submit">新增盒子</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -34,8 +36,39 @@
|
|||||||
<article class="box-card">
|
<article class="box-card">
|
||||||
<h4>{{ item.box.name }}</h4>
|
<h4>{{ item.box.name }}</h4>
|
||||||
<p>{{ item.box.description or '暂无描述' }}</p>
|
<p>{{ item.box.description or '暂无描述' }}</p>
|
||||||
<p>已使用: {{ item.used_count }}/{{ item.box.slot_capacity }}</p>
|
<p>编号前缀: {{ item.box.slot_prefix }} | 范围: {{ item.slot_range }}</p>
|
||||||
|
<p>已启用: {{ item.used_count }}/{{ item.box.slot_capacity }}</p>
|
||||||
|
|
||||||
|
<div class="card-actions">
|
||||||
<a class="btn" href="{{ url_for('view_box', box_id=item.box.id) }}">进入列表</a>
|
<a class="btn" href="{{ url_for('view_box', box_id=item.box.id) }}">进入列表</a>
|
||||||
|
<form method="post" action="{{ url_for('delete_box', box_id=item.box.id) }}" onsubmit="return confirm('确认删除盒子及内部全部记录吗?')">
|
||||||
|
<button class="btn btn-danger" type="submit">删除</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="box-overview">
|
||||||
|
<summary>概览(已启用序号和名称)</summary>
|
||||||
|
{% if item.overview_rows %}
|
||||||
|
<ul>
|
||||||
|
{% for row in item.overview_rows %}
|
||||||
|
<li>{{ row.slot_code }} - {{ row.name }} ({{ row.part_no }})</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p>暂无启用记录</p>
|
||||||
|
{% endif %}
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="box-overview">
|
||||||
|
<summary>设置(改名/前缀/起始号)</summary>
|
||||||
|
<form class="new-box-form compact" method="post" action="{{ url_for('update_box', box_id=item.box.id) }}">
|
||||||
|
<input type="text" name="name" value="{{ item.box.name }}" required>
|
||||||
|
<input type="text" name="slot_prefix" value="{{ item.box.slot_prefix }}" required>
|
||||||
|
<input type="number" name="start_number" min="0" value="{{ item.box.start_number }}" required>
|
||||||
|
<input type="text" name="description" value="{{ item.box.description or '' }}">
|
||||||
|
<button class="btn" type="submit">保存设置</button>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
</article>
|
</article>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>当前分类还没有盒子,先新增一个。</p>
|
<p>当前分类还没有盒子,先新增一个。</p>
|
||||||
|
|||||||
@@ -32,16 +32,19 @@
|
|||||||
<th>名称</th>
|
<th>名称</th>
|
||||||
<th>库存</th>
|
<th>库存</th>
|
||||||
<th>位置</th>
|
<th>位置</th>
|
||||||
|
<th>状态</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for c in results %}
|
{% for row in results %}
|
||||||
|
{% set c = row.component %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ c.part_no }}</td>
|
<td>{{ c.part_no }}</td>
|
||||||
<td>{{ c.name }}</td>
|
<td>{{ c.name }}</td>
|
||||||
<td>{{ c.quantity }}</td>
|
<td>{{ c.quantity }}</td>
|
||||||
<td>盒 {{ c.box_id }} / 格 {{ c.slot_index }}</td>
|
<td>{{ row.box_name }} / {{ row.slot_code }}</td>
|
||||||
|
<td>{% if c.is_enabled %}启用{% else %}停用{% endif %}</td>
|
||||||
<td><a href="{{ url_for('edit_component', box_id=c.box_id, slot=c.slot_index) }}">编辑</a></td>
|
<td><a href="{{ url_for('edit_component', box_id=c.box_id, slot=c.slot_index) }}">编辑</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
Reference in New Issue
Block a user