ok
This commit is contained in:
170
inventory_check/1.py
Normal file
170
inventory_check/1.py
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
|
||||||
|
|
||||||
|
from flask import *
|
||||||
|
#from client import Client
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
import sqlite3
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
DB = 'inventory.db'
|
||||||
|
|
||||||
|
def getmd5(data):
|
||||||
|
s = ''
|
||||||
|
for i in data:
|
||||||
|
s += str(i).strip()
|
||||||
|
r = hashlib.md5(s.encode(encoding='UTF-8')).hexdigest()
|
||||||
|
return r
|
||||||
|
|
||||||
|
def formatted_time():
|
||||||
|
return time.strftime("%Y/%m/%d", time.localtime())
|
||||||
|
|
||||||
|
def dbsetup(data):
|
||||||
|
"""
|
||||||
|
'{
|
||||||
|
"25":{"location": 28, "tag": "7.61/8.10", "amount": 3 },
|
||||||
|
"26":{"location": 29, "tag": "7.59/8.10", "amount": 3 }
|
||||||
|
}'
|
||||||
|
"""
|
||||||
|
conn = sqlite3.connect(DB)
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("DROP TABLE IF EXISTS inventory")
|
||||||
|
conn.commit()
|
||||||
|
c.execute('''CREATE TABLE IF NOT EXISTS inventory
|
||||||
|
(ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
SORT INT,
|
||||||
|
LOCATION INT,
|
||||||
|
TAG TEXT,
|
||||||
|
AMOUNT INT,
|
||||||
|
NAME TEXT,
|
||||||
|
DATE TEXT);''')
|
||||||
|
conn.commit()
|
||||||
|
for e in data.keys():
|
||||||
|
v = (int(e), data[e]["location"], data[e]["tag"], data[e]["amount"])
|
||||||
|
c.execute('''INSERT INTO inventory
|
||||||
|
(SORT,LOCATION,TAG,AMOUNT) \
|
||||||
|
VALUES {v}'''.format(v=v))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def dbinsert(tb='inventory', v=()):
|
||||||
|
conn = sqlite3.connect(DB)
|
||||||
|
c = conn.cursor()
|
||||||
|
#c.execute('''INSERT INTO {tb}
|
||||||
|
# (ID,NU,MOULD,SPEC,AMOUNT,OUTDATE,RECEIVRE,MANAGER,\
|
||||||
|
# FAC,SERIAL,STATE,INDATE,COLLECTOR,COMMET,MD5) \
|
||||||
|
# VALUES {v}'''.format(tb=tb, v=v))
|
||||||
|
c.execute("INSERT INTO {tb} VALUES {v};".format(tb=tb, v=v))
|
||||||
|
#(2, 'Allen', 25, 'Texas', 15000.00 )
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def dbselect(tb='inventory'):
|
||||||
|
conn = sqlite3.connect(DB)
|
||||||
|
c = conn.cursor()
|
||||||
|
# c.execute("SELECT ID,NU,MOULD,SPEC,AMOUNT,OUTDATE,RECEIVRE,\
|
||||||
|
# MANAGER,FAC,SERIAL,STATE,INDATE,COLLECTOR,COMMET,MD5 from {tb};".format(tb=tb))
|
||||||
|
c.execute("SELECT * FROM {tb};".format(tb=tb))
|
||||||
|
return c.fetchall()
|
||||||
|
|
||||||
|
def dbupdate(tb='inventory', d={'ID':123123,'MD5':'asdasdasdasd'}):
|
||||||
|
conn = sqlite3.connect(DB)
|
||||||
|
c = conn.cursor()
|
||||||
|
for e in d:
|
||||||
|
if isinstance(d[e], str):
|
||||||
|
c.execute("UPDATE {tb} SET {h} = '{r}' WHERE ID={id};".format(tb=tb, h=e, r=d[e], id=d['ID']))
|
||||||
|
else:
|
||||||
|
c.execute("UPDATE {tb} SET {h} = {r} WHERE ID={id};".format(tb=tb, h=e, r=d[e], id=d['ID']))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def dbdelete(tb='inventory', i=123123):
|
||||||
|
conn = sqlite3.connect(DB)
|
||||||
|
c = conn.cursor()
|
||||||
|
try:
|
||||||
|
c.execute("DELETE FROM {tb} WHERE ID={i};".format(tb=tb, i=i))
|
||||||
|
LOG('已删除该条目……')
|
||||||
|
except:
|
||||||
|
LOG('没有该条目……')
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
LOG('已删除……')
|
||||||
|
|
||||||
|
def dbsearchhead(tb='inventory', h='NU', s='3'):
|
||||||
|
conn = sqlite3.connect(DB)
|
||||||
|
c = conn.cursor()
|
||||||
|
#c.execute("SELECT ID,NU,MOULD,SPEC,AMOUNT,OUTDATE,RECEIVRE,MANAGER,\
|
||||||
|
#FAC,SERIAL,STATE,INDATE,COLLECTOR,COMMET,MD5 from {tb};".format(tb=tb))
|
||||||
|
c.execute("SELECT * FROM {tb} WHERE {h} GLOB '*{s}*';".format(tb=tb, h=h, s=s))
|
||||||
|
return c.fetchall()
|
||||||
|
|
||||||
|
def dbsearchtime(tb='inventory', t=(111111,222222)):
|
||||||
|
conn = sqlite3.connect(DB)
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT * FROM {tb} WHERE ID >= {a} AND ID >= {b} ;".format(tb=tb, a=t[0], b=t[1]))
|
||||||
|
return c.fetchall()
|
||||||
|
|
||||||
|
def dbadd(tb='inventory', d={'ID':123123,'MD5':'asdasdasdasd'}):
|
||||||
|
conn = sqlite3.connect(DB)
|
||||||
|
c = conn.cursor()
|
||||||
|
v = (d['ID'],' ',' ',' ',1,'','','','','','','','','','',0,'')
|
||||||
|
try:
|
||||||
|
c.execute("INSERT INTO {tb} VALUES {v};".format(tb=tb, v=v))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
#(2, 'Allen', 25, 'Texas', 15000.00 )
|
||||||
|
dbupdate(tb=tb, d=d)
|
||||||
|
|
||||||
|
def dbnothave(tb='inventory', md5='md5'):
|
||||||
|
conn = sqlite3.connect(DB)
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT * FROM {tb} WHERE MD5 = '{md5}';".format(tb=tb, md5=md5))
|
||||||
|
if c.fetchall() == []:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@app.route('/inventory', methods=['POST','GET'])
|
||||||
|
|
||||||
|
# from flask import Flask, request, jsonify
|
||||||
|
def inventory():
|
||||||
|
if request.method == 'POST':
|
||||||
|
# 接收JSON数据
|
||||||
|
data = request.get_json() # '{"sort": 25, "location": 28, "tag": "7.61/8.10", "name": "Bob"}'
|
||||||
|
print("Received data:", data)
|
||||||
|
# 处理数据(示例:简单地返回接收到的数据)
|
||||||
|
return jsonify({"message": "JSON data received", "yourData": data})
|
||||||
|
else:
|
||||||
|
# 发送JSON数据
|
||||||
|
response_data = {
|
||||||
|
"name": "Alice",
|
||||||
|
"age": 25,
|
||||||
|
"city": "Los Angeles"
|
||||||
|
}
|
||||||
|
return jsonify(response_data)
|
||||||
|
|
||||||
|
@app.route('/inventory/init', methods=['POST','GET'])
|
||||||
|
def inventory_init():
|
||||||
|
if request.method == 'POST':
|
||||||
|
# 接收JSON数据
|
||||||
|
data = request.get_json() # '{25:{"location": 28, "tag": "7.61/8.10", "amount": 3 }, 26:{"location": 29, "tag": "7.59/8.10", "amount": 3 }}'
|
||||||
|
try:
|
||||||
|
dbsetup(data)
|
||||||
|
return "请求成功", 200
|
||||||
|
except:
|
||||||
|
return "无法理解", 400
|
||||||
|
else:
|
||||||
|
# 发送JSON数据
|
||||||
|
response_data = {
|
||||||
|
"name": "Alice",
|
||||||
|
"age": 25,
|
||||||
|
"city": "Los Angeles"
|
||||||
|
}
|
||||||
|
return jsonify(response_data)
|
18
inventory_check/__init__.py
Normal file
18
inventory_check/__init__.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
:author: luffmims
|
||||||
|
:url: http://yum2.cc
|
||||||
|
:copyright: © 2025 luffmims <luffmims@hotmaill.com>
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
from flask import Flask
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
app = Flask('inventory_flask')
|
||||||
|
app.config.from_pyfile('settings.py')
|
||||||
|
app.jinja_env.trim_blocks = True
|
||||||
|
app.jinja_env.lstrip_blocks = True
|
||||||
|
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
from inventory_flask import views, errors, commands
|
94
inventory_check/app.py
Normal file
94
inventory_check/app.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
|
||||||
|
|
||||||
|
from flask import Flask, redirect, render_template, request, url_for
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from openpyxl import load_workbook
|
||||||
|
from flask_bootstrap import Bootstrap5
|
||||||
|
from wtforms import Form, IntegerField, StringField, SubmitField
|
||||||
|
from wtforms.validators import NumberRange
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///foo.db'
|
||||||
|
app.secret_key = 'qweqweqwe'
|
||||||
|
bootstrap = Bootstrap5(app)
|
||||||
|
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
class Inventory(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
sort = db.Column(db.Integer)
|
||||||
|
location = db.Column(db.Integer)
|
||||||
|
tag = db.Column(db.Text)
|
||||||
|
amount = db.Column(db.Integer)
|
||||||
|
name = db.Column(db.Text)
|
||||||
|
mtime = db.Column(db.Date)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
class CheckForm(Form):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.sort = IntegerField('sort')
|
||||||
|
self.location = IntegerField('location')
|
||||||
|
self.tag = StringField('tag')
|
||||||
|
self.amount = IntegerField('amount', validators=[NumberRange(min=0)])
|
||||||
|
self.name = StringField('name')
|
||||||
|
self.mtime = StringField('mtime')
|
||||||
|
tag = StringField('tag')
|
||||||
|
minusbuton = SubmitField('minus')
|
||||||
|
amount = IntegerField('amount', validators=[NumberRange(min=0)])
|
||||||
|
plusbuton = SubmitField('plus')
|
||||||
|
goback = SubmitField('back')
|
||||||
|
confim = SubmitField('right')
|
||||||
|
goahead = SubmitField('ahead')
|
||||||
|
def import_from_xlsx(file_path):
|
||||||
|
with app.app_context():
|
||||||
|
db.drop_all()
|
||||||
|
db.create_all()
|
||||||
|
# 加载Excel工作簿
|
||||||
|
wb = load_workbook(filename=file_path, data_only=True)
|
||||||
|
# 加载工作簿,设置 data_only=True
|
||||||
|
ws = wb.active
|
||||||
|
|
||||||
|
# 遍历工作表中的每一行
|
||||||
|
for row in ws.iter_rows(min_row=3, values_only=True): # 假设第一行是标题行
|
||||||
|
new_inventory = Inventory(
|
||||||
|
sort=row[0],
|
||||||
|
location=row[1],
|
||||||
|
tag=row[2],
|
||||||
|
amount=row[3],
|
||||||
|
|
||||||
|
)
|
||||||
|
db.session.add(new_inventory)
|
||||||
|
|
||||||
|
# 提交事务
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def db_to_turple():
|
||||||
|
with app.app_context():
|
||||||
|
inventory = Inventory.query.all()
|
||||||
|
for e in inventory:
|
||||||
|
print(e.sort, e.location, e.tag, e.amount, e.name, e.mtime)
|
||||||
|
|
||||||
|
@app.route('/upload', methods=['GET', 'POST'])
|
||||||
|
def upload_form():
|
||||||
|
if request.method == 'POST':
|
||||||
|
# 文件上传逻辑
|
||||||
|
pass
|
||||||
|
return render_template('upload.html')
|
||||||
|
|
||||||
|
@app.route('/import', methods=['POST'])
|
||||||
|
def import_file():
|
||||||
|
if request.method == 'POST':
|
||||||
|
file = request.files['file']
|
||||||
|
if file:
|
||||||
|
file_path = 'data.xlsx'
|
||||||
|
file.save(file_path)
|
||||||
|
import_from_xlsx(file_path)
|
||||||
|
db_to_turple()
|
||||||
|
return '文件导入成功!'
|
||||||
|
|
||||||
|
return redirect(url_for('upload_form'))
|
||||||
|
|
47
inventory_check/commands.py
Normal file
47
inventory_check/commands.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
:author: luffmims
|
||||||
|
:url: http://yum2.cc
|
||||||
|
:copyright: © 2025 luffmims <luffmims@hotmaill.com>
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import click
|
||||||
|
|
||||||
|
from inventory_check import app, db
|
||||||
|
from inventory_check.models import Inventory
|
||||||
|
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
@click.option('--drop', is_flag=True, help='Create after drop.')
|
||||||
|
def initdb(drop):
|
||||||
|
"""Initialize the database."""
|
||||||
|
if drop:
|
||||||
|
click.confirm('This operation will delete the database, do you want to continue?', abort=True)
|
||||||
|
db.drop_all()
|
||||||
|
click.echo('Drop tables.')
|
||||||
|
db.create_all()
|
||||||
|
click.echo('Initialized database.')
|
||||||
|
|
||||||
|
|
||||||
|
# @app.cli.command()
|
||||||
|
# @click.option('--count', default=20, help='Quantity of messages, default is 20.')
|
||||||
|
# def forge(count):
|
||||||
|
# """Generate fake messages."""
|
||||||
|
# from faker import Faker
|
||||||
|
|
||||||
|
# db.drop_all()
|
||||||
|
# db.create_all()
|
||||||
|
|
||||||
|
# fake = Faker()
|
||||||
|
# click.echo('Working...')
|
||||||
|
|
||||||
|
# for i in range(count):
|
||||||
|
# message = Message(
|
||||||
|
# name=fake.name(),
|
||||||
|
# body=fake.sentence(),
|
||||||
|
# timestamp=fake.date_time_this_year()
|
||||||
|
# )
|
||||||
|
# db.session.add(message)
|
||||||
|
|
||||||
|
# db.session.commit()
|
||||||
|
# click.echo('Created %d fake messages.' % count)
|
20
inventory_check/errors.py
Normal file
20
inventory_check/errors.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
:author: luffmims
|
||||||
|
:url: http://yum2.cc
|
||||||
|
:copyright: © 2025 luffmims <luffmims@hotmaill.com>
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
from flask import render_template
|
||||||
|
|
||||||
|
from inventory_check import app
|
||||||
|
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(e):
|
||||||
|
return render_template('errors/404.html'), 404
|
||||||
|
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def internal_server_error(e):
|
||||||
|
return render_template('errors/500.html'), 500
|
33
inventory_check/forms.py
Normal file
33
inventory_check/forms.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
:author: luffmims
|
||||||
|
:url: http://yum2.cc
|
||||||
|
:copyright: © 2025 luffmims <luffmims@hotmaill.com>
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import IntegerField, StringField, SubmitField
|
||||||
|
from wtforms.validators import NumberRange
|
||||||
|
|
||||||
|
# class HelloForm(FlaskForm):
|
||||||
|
# name = StringField('Name', validators=[DataRequired(), Length(1, 20)])
|
||||||
|
# body = TextAreaField('Message', validators=[DataRequired(), Length(1, 200)])
|
||||||
|
# submit = SubmitField()
|
||||||
|
|
||||||
|
class CheckForm(FlaskForm):
|
||||||
|
tag = ''
|
||||||
|
amount = 0
|
||||||
|
id = 0
|
||||||
|
def __init__(self, Inventory):
|
||||||
|
super().__init__(Inventory)
|
||||||
|
self.id = Inventory.id
|
||||||
|
self.tag = Inventory.tag
|
||||||
|
self.amount = Inventory.amount
|
||||||
|
|
||||||
|
showtag = StringField(tag)
|
||||||
|
minusbuton = SubmitField('-')
|
||||||
|
amount = IntegerField('amount', validators=[NumberRange(min=0)])
|
||||||
|
plusbuton = SubmitField('+')
|
||||||
|
goback = SubmitField('back')
|
||||||
|
confim = SubmitField('right')
|
||||||
|
goahead = SubmitField('ahead')
|
70
inventory_check/models.py
Normal file
70
inventory_check/models.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
:author: luffmims
|
||||||
|
:url: http://yum2.cc
|
||||||
|
:copyright: © 2025 luffmims <luffmims@hotmaill.com>
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# from datetime import datetime
|
||||||
|
|
||||||
|
from inventory_check import app, db
|
||||||
|
from openpyxl import Workbook, load_workbook
|
||||||
|
|
||||||
|
class Inventory(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
sort = db.Column(db.Integer)
|
||||||
|
location = db.Column(db.Integer, default=0)
|
||||||
|
tag = db.Column(db.Text)
|
||||||
|
amount = db.Column(db.Integer)
|
||||||
|
checked = db.Column(db.Boolean, default=False)
|
||||||
|
name = db.Column(db.Text)
|
||||||
|
# mtime = db.Column(db.Date)
|
||||||
|
|
||||||
|
|
||||||
|
def import_from_xlsx(file_path):
|
||||||
|
with app.app_context():
|
||||||
|
db.drop_all()
|
||||||
|
db.create_all()
|
||||||
|
# 加载Excel工作簿
|
||||||
|
wb = load_workbook(filename=file_path, data_only=True)
|
||||||
|
# 加载工作簿,设置 data_only=True
|
||||||
|
ws = wb.active
|
||||||
|
|
||||||
|
# 遍历工作表中的每一行
|
||||||
|
for row in ws.iter_rows(min_row=2, values_only=True): # 假设第一行是标题行
|
||||||
|
new_inventory = Inventory(
|
||||||
|
sort=row[0],
|
||||||
|
location=row[1],
|
||||||
|
tag=row[2],
|
||||||
|
amount=row[3],
|
||||||
|
)
|
||||||
|
db.session.add(new_inventory)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
"""
|
||||||
|
This function exports the inventory data from the database to an Excel file named 'checked.xlsx'.
|
||||||
|
It queries all inventory records, creates a new Excel workbook and sheet named 'Inventory',
|
||||||
|
sets the column headers, appends each inventory's details to the sheet, and finally saves the workbook.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
def import_to_xlsx():
|
||||||
|
inventorys = Inventory.query.all() # 查询所有数据, and sort data by sort
|
||||||
|
# 创建一个工作簿
|
||||||
|
wb = Workbook()
|
||||||
|
# 创建一个工作表
|
||||||
|
ws = wb.active
|
||||||
|
# 设置工作表名称
|
||||||
|
ws.title = "Inventory"
|
||||||
|
# 设置工作表列名
|
||||||
|
ws.append(["sort", "location", "tag", "amount", "name"])
|
||||||
|
# 写入数据
|
||||||
|
for inventory in inventorys:
|
||||||
|
ws.append([inventory.sort, inventory.location, inventory.tag, inventory.amount, inventory.name])
|
||||||
|
# 保存工作簿
|
||||||
|
wb.save("checked.xlsx")
|
25
inventory_check/settings.py
Normal file
25
inventory_check/settings.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
:author: luffmims
|
||||||
|
:url: http://yum2.cc
|
||||||
|
:copyright: © 2025 luffmims <luffmims@hotmaill.com>
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from inventory_check import app
|
||||||
|
|
||||||
|
# sqlite URI compatible
|
||||||
|
WIN = sys.platform.startswith('win')
|
||||||
|
if WIN:
|
||||||
|
prefix = 'sqlite:///'
|
||||||
|
else:
|
||||||
|
prefix = 'sqlite:////'
|
||||||
|
|
||||||
|
|
||||||
|
dev_db = prefix + os.path.join(os.path.dirname(app.root_path), 'data.db')
|
||||||
|
|
||||||
|
SECRET_KEY = os.getenv('SECRET_KEY', 'secret string')
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URI', dev_db)
|
32
inventory_check/static/css/style.css
Normal file
32
inventory_check/static/css/style.css
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
main {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
width: 4ch;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
width: 300px;
|
||||||
|
height: 3px;
|
||||||
|
}
|
||||||
|
.grail{
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.handler {
|
||||||
|
width: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: space-between;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
button, input {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
BIN
inventory_check/static/favicon.ico
Normal file
BIN
inventory_check/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
45
inventory_check/static/js/script.js
Normal file
45
inventory_check/static/js/script.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
function increment() {
|
||||||
|
var amount = document.getElementById("inventory-amount");
|
||||||
|
amount.value = parseInt(amount.value) + 1;
|
||||||
|
}
|
||||||
|
function decrement() {
|
||||||
|
var amount = document.getElementById("inventory-amount");
|
||||||
|
if (amount.value > 0) {
|
||||||
|
amount.value = parseInt(amount.value) - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function checknext() {
|
||||||
|
var amount = document.getElementById("inventory-amount");
|
||||||
|
// 获取ID为"forid"的元素
|
||||||
|
var idelement = document.getElementById("inventory-id");
|
||||||
|
// 获取该元素的文本内容,并解析为整数
|
||||||
|
var id = parseInt(idelement.innerText, 10); // 第二个参数10表示解析的基数,这里是十进制
|
||||||
|
var tagelement = document.getElementById("inventory-tag");
|
||||||
|
var tag = tagelement.innerText;
|
||||||
|
var name = "A"
|
||||||
|
var msg = document.getElementById("flash-msg");
|
||||||
|
// var params = new URLSearchParams({
|
||||||
|
// checked: 1,
|
||||||
|
// id: id,
|
||||||
|
// amount: amount.value
|
||||||
|
// });
|
||||||
|
// var url = `/check?${params.toString()}`;
|
||||||
|
fetch('/get_inventory', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({id: id, amount: amount.value, name: name})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// console.log('Success:', data);
|
||||||
|
amount.value = data.amount;
|
||||||
|
tagelement.innerText = data.tag;
|
||||||
|
idelement.innerText = data.id;
|
||||||
|
msg.innerText = data.checkedone + " checked " + data.checkedamount;
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error:', error));
|
||||||
|
|
||||||
|
}
|
34
inventory_check/templates/base.html
Normal file
34
inventory_check/templates/base.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>盘库助手</title>
|
||||||
|
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" type="text/css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="grail">
|
||||||
|
<header>
|
||||||
|
<h1>
|
||||||
|
<a href="" ><strong>CHECK</strong></a>
|
||||||
|
<small>it</small>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
<footer>
|
||||||
|
{% block footer %}
|
||||||
|
<small> © 2025 <a href="http://yum2.cc" title="Written by luffmims">luffmims</a> /
|
||||||
|
|
||||||
|
</small>
|
||||||
|
<p><a id="bottom" href="#" title="Go Top">↑</a></p>
|
||||||
|
{% endblock %}
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{ url_for('static', filename='js/script.js') }}"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
28
inventory_check/templates/check.html
Normal file
28
inventory_check/templates/check.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% from 'bootstrap/form.html' import render_form %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="flash-msg">
|
||||||
|
Let's go!
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="handler">
|
||||||
|
<button type="button">返回</button>
|
||||||
|
<div></div><div><p id="inventory-id" hidden>{{ inventory.id }}</p></div>
|
||||||
|
</div>
|
||||||
|
<h1 id="inventory-tag">{{ inventory.tag }}</h1>
|
||||||
|
<hr>
|
||||||
|
<div class="handler">
|
||||||
|
<button onclick="decrement()" type="button">减一</button>
|
||||||
|
<input type="number" id="inventory-amount" value="{{ inventory.amount }}" min="0">
|
||||||
|
<button onclick="increment()" type="button">加一</button>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="handler">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<button onclick="checknext()" type="button">确认,下一个</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
0
inventory_check/templates/count.html
Normal file
0
inventory_check/templates/count.html
Normal file
11
inventory_check/templates/errors/404.html
Normal file
11
inventory_check/templates/errors/404.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}404 Error{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p class="text-center">Page Not Found</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
<a href="{{ url_for('index') }}">← Go Back</a>
|
||||||
|
{% endblock %}
|
11
inventory_check/templates/errors/500.html
Normal file
11
inventory_check/templates/errors/500.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}500 Error{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p class="text-center">Internal Server Error</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
<a href="{{ url_for('index') }}">← Go Back</a>
|
||||||
|
{% endblock %}
|
10
inventory_check/templates/index.html
Normal file
10
inventory_check/templates/index.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% from 'bootstrap/form.html' import render_form %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h5> messages
|
||||||
|
<small class="float-right">
|
||||||
|
<a href="#bottom" title="Go Bottom">↓</a>
|
||||||
|
</small>
|
||||||
|
</h5>
|
||||||
|
{% endblock %}
|
15
inventory_check/templates/upload.html
Normal file
15
inventory_check/templates/upload.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}上传Excel文件{% endblock %}
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
<style type="text/css">
|
||||||
|
.important { color: #336699; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="important">上传Excel文件</h1>
|
||||||
|
<form method=post enctype=multipart/form-data action="/import">
|
||||||
|
<input type=file name=file>
|
||||||
|
<input type=submit value=上传>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
143
inventory_check/test.html
Normal file
143
inventory_check/test.html
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<title>Check!</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 0;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.check-form {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.check-title {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.check-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.sub-title {
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.alert {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
#bottom {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="container">
|
||||||
|
<header>
|
||||||
|
<h1 class="text-center display-4">
|
||||||
|
<a href="" class="text-success"><strong>CHECK</strong></a>
|
||||||
|
<small class="text-muted sub-title">it</small>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
<div class="alert alert-info" id="flash-msg" role="alert">
|
||||||
|
Let's go!
|
||||||
|
</div>
|
||||||
|
<div class="check-form">
|
||||||
|
<div class="check-title" id="inventory-tag">
|
||||||
|
inventory.tag
|
||||||
|
</div>
|
||||||
|
<p id="inventory-id" hidden>inventory.id</p>
|
||||||
|
<div style="justify-content: space-between;">
|
||||||
|
<button onclick="decrement()" type="button" class="btn btn-info">-</button>
|
||||||
|
<input type="number" id="inventory-amount" value="nventory.amount" min="0" style="width: 7ch;">
|
||||||
|
<button onclick="increment()" type="button" class="btn btn-info">+</button>
|
||||||
|
</div>
|
||||||
|
<div class="check-footer">
|
||||||
|
<button type="button" class="btn btn-light">back</button>
|
||||||
|
<button type="button" class="btn btn-success">ok</button>
|
||||||
|
<button onclick="checknext()" type="button" class="btn btn-success">next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="text-center">
|
||||||
|
|
||||||
|
<small> © 2025 <a href="http://yum2.cc" title="Written by luffmims">luffmims</a> /
|
||||||
|
|
||||||
|
</small>
|
||||||
|
<p><a id="bottom" href="#" title="Go Top">↑</a></p>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
<script>
|
||||||
|
function increment() {
|
||||||
|
var amount = document.getElementById("inventory-amount");
|
||||||
|
amount.value = parseInt(amount.value) + 1;
|
||||||
|
}
|
||||||
|
function decrement() {
|
||||||
|
var amount = document.getElementById("inventory-amount");
|
||||||
|
if (amount.value > 0) {
|
||||||
|
amount.value = parseInt(amount.value) - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function checknext() {
|
||||||
|
var amount = document.getElementById("inventory-amount");
|
||||||
|
// 获取ID为"forid"的元素
|
||||||
|
var idelement = document.getElementById("inventory-id");
|
||||||
|
// 获取该元素的文本内容,并解析为整数
|
||||||
|
var id = parseInt(idelement.innerText, 10); // 第二个参数10表示解析的基数,这里是十进制
|
||||||
|
var tagelement = document.getElementById("inventory-tag");
|
||||||
|
var tag = tagelement.innerText;
|
||||||
|
var name = "A"
|
||||||
|
var msg = document.getElementById("flash-msg");
|
||||||
|
// var params = new URLSearchParams({
|
||||||
|
// checked: 1,
|
||||||
|
// id: id,
|
||||||
|
// amount: amount.value
|
||||||
|
// });
|
||||||
|
// var url = `/check?${params.toString()}`;
|
||||||
|
fetch('/get_inventory', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({id: id, amount: amount.value, name: name})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// console.log('Success:', data);
|
||||||
|
amount.value = data.amount;
|
||||||
|
tagelement.innerText = data.tag;
|
||||||
|
idelement.innerText = data.id;
|
||||||
|
msg.innerText = data.checkedone + " checked " + data.checkedamount;
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error:', error));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
60
inventory_check/test.py
Normal file
60
inventory_check/test.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
from flask import Flask, redirect, render_template, request, url_for
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from sqlalchemy.orm import DeclarativeBase
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
db = SQLAlchemy(model_class=Base)
|
||||||
|
|
||||||
|
# create the app
|
||||||
|
app = Flask(__name__)
|
||||||
|
# configure the SQLite database, relative to the app instance folder
|
||||||
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///project.db"
|
||||||
|
# initialize the app with the extension
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
from sqlalchemy import Integer, String
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
username: Mapped[str] = mapped_column(unique=True)
|
||||||
|
email: Mapped[str]
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
@app.route("/users")
|
||||||
|
def user_list():
|
||||||
|
users = db.session.execute(db.select(User).order_by(User.username)).scalars()
|
||||||
|
return render_template("user/list.html", users=users)
|
||||||
|
|
||||||
|
@app.route("/users/create", methods=["GET", "POST"])
|
||||||
|
def user_create():
|
||||||
|
if request.method == "POST":
|
||||||
|
user = User(
|
||||||
|
username=request.form["username"],
|
||||||
|
email=request.form["email"],
|
||||||
|
)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for("user_detail", id=user.id))
|
||||||
|
|
||||||
|
return render_template("user/create.html")
|
||||||
|
|
||||||
|
@app.route("/user/<int:id>")
|
||||||
|
def user_detail(id):
|
||||||
|
user = db.get_or_404(User, id)
|
||||||
|
return render_template("user/detail.html", user=user)
|
||||||
|
|
||||||
|
@app.route("/user/<int:id>/delete", methods=["GET", "POST"])
|
||||||
|
def user_delete(id):
|
||||||
|
user = db.get_or_404(User, id)
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
db.session.delete(user)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for("user_list"))
|
||||||
|
|
||||||
|
return render_template("user/delete.html", user=user)
|
106
inventory_check/views.py
Normal file
106
inventory_check/views.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
:author: luffmims
|
||||||
|
:url: http://yum2.cc
|
||||||
|
:copyright: © 2025 luffmims <luffmims@hotmaill.com>
|
||||||
|
:license: MIT, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
from flask import flash, jsonify, redirect, request, url_for, render_template
|
||||||
|
|
||||||
|
from inventory_check import app, db
|
||||||
|
from inventory_check.models import import_from_xlsx, Inventory
|
||||||
|
from werkzeug.exceptions import BadRequest
|
||||||
|
|
||||||
|
# @app.route('/', methods=['GET', 'POST'])
|
||||||
|
# def index():
|
||||||
|
# messages = Message.query.order_by(Message.timestamp.desc()).all()
|
||||||
|
# form = HelloForm()
|
||||||
|
# if form.validate_on_submit():
|
||||||
|
# name = form.name.data
|
||||||
|
# body = form.body.data
|
||||||
|
# message = Message(body=body, name=name)
|
||||||
|
# db.session.add(message)
|
||||||
|
# db.session.commit()
|
||||||
|
# flash('Your message have been sent to the world!')
|
||||||
|
# return redirect(url_for('index'))
|
||||||
|
# return render_template('index.html', form=form, messages=messages)
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@app.route('/check', methods=['GET'])
|
||||||
|
def check():
|
||||||
|
inventory = Inventory.query.filter_by(checked=False).first()
|
||||||
|
print(inventory)
|
||||||
|
if not inventory:
|
||||||
|
return "All checked"
|
||||||
|
return render_template('check.html',inventory=inventory)
|
||||||
|
|
||||||
|
@app.route('/get_inventory', methods=['POST'])
|
||||||
|
def update_inventory():
|
||||||
|
try:
|
||||||
|
# 获取并验证表单数据
|
||||||
|
data = request.get_json()
|
||||||
|
inventory_id = data['id']
|
||||||
|
amount = data['amount']
|
||||||
|
name = data['name']
|
||||||
|
|
||||||
|
if not inventory_id or not amount:
|
||||||
|
raise BadRequest('Missing inventory id or amount')
|
||||||
|
|
||||||
|
# 查询并更新库存对象
|
||||||
|
inventory = Inventory.query.get(inventory_id)
|
||||||
|
if not inventory:
|
||||||
|
raise BadRequest('Inventory not found')
|
||||||
|
checkedone = inventory.tag
|
||||||
|
inventory.amount = amount
|
||||||
|
inventory.name = name
|
||||||
|
inventory.checked = True
|
||||||
|
|
||||||
|
# 提交更改
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# 显示成功消息
|
||||||
|
# flash(f'{inventory.tag} checked and updated')
|
||||||
|
id = inventory_id + 1
|
||||||
|
inventory = Inventory.query.get(id)
|
||||||
|
if not inventory:
|
||||||
|
raise BadRequest('Inventory not found')
|
||||||
|
# 返回更新后的库存信息
|
||||||
|
return jsonify({
|
||||||
|
'id': inventory.id,
|
||||||
|
'tag': inventory.tag,
|
||||||
|
'amount': inventory.amount,
|
||||||
|
'checkedone': checkedone,
|
||||||
|
'checkedamount': amount
|
||||||
|
})
|
||||||
|
|
||||||
|
except BadRequest as e:
|
||||||
|
# 处理错误情况
|
||||||
|
flash(str(e))
|
||||||
|
return jsonify({'error': str(e)}), 400
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# 处理其他可能的错误
|
||||||
|
db.session.rollback()
|
||||||
|
flash('An error occurred while updating the inventory')
|
||||||
|
return jsonify({'error': 'Internal server error'}), 500
|
||||||
|
|
||||||
|
@app.route('/upload', methods=['GET', 'POST'])
|
||||||
|
def upload_form():
|
||||||
|
if request.method == 'POST':
|
||||||
|
# 文件上传逻辑
|
||||||
|
pass
|
||||||
|
return render_template('upload.html')
|
||||||
|
|
||||||
|
@app.route('/import', methods=['POST'])
|
||||||
|
def import_file():
|
||||||
|
if request.method == 'POST':
|
||||||
|
file = request.files['file']
|
||||||
|
if file:
|
||||||
|
file_path = 'data.xlsx'
|
||||||
|
file.save(file_path)
|
||||||
|
import_from_xlsx(file_path)
|
||||||
|
return '文件导入成功!'
|
||||||
|
|
||||||
|
return redirect(url_for('upload_form'))
|
BIN
游芯盘点表.xlsx
Normal file
BIN
游芯盘点表.xlsx
Normal file
Binary file not shown.
Reference in New Issue
Block a user