一个手机快速搜索商品的网页
This commit is contained in:
Binary file not shown.
+179
@@ -0,0 +1,179 @@
|
||||
from fastapi import FastAPI, UploadFile, File, Query
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import sqlite3
|
||||
import pandas as pd
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
DB_PATH = os.path.join(os.getcwd(), "data", "products.db")
|
||||
os.makedirs(os.path.join(os.getcwd(), "data"), exist_ok=True)
|
||||
|
||||
def get_conn():
|
||||
conn = sqlite3.connect(DB_PATH, check_same_thread=False)
|
||||
try:
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute("PRAGMA synchronous=NORMAL")
|
||||
except Exception:
|
||||
pass
|
||||
return conn
|
||||
|
||||
def init_db():
|
||||
conn = get_conn()
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"""
|
||||
create table if not exists products (
|
||||
barcode text primary key,
|
||||
name text,
|
||||
purchase_price real,
|
||||
sale_price real,
|
||||
category text,
|
||||
created_at text,
|
||||
source_file text
|
||||
)
|
||||
"""
|
||||
)
|
||||
cur.execute("create index if not exists idx_products_name on products(name)")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
init_db()
|
||||
|
||||
app = FastAPI()
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def root():
|
||||
return open(os.path.join("static", "index.html"), "r", encoding="utf-8").read()
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
def normalize_price(v):
|
||||
if v is None:
|
||||
return None
|
||||
try:
|
||||
s = str(v).strip()
|
||||
if s == "":
|
||||
return None
|
||||
return float(s)
|
||||
except:
|
||||
return None
|
||||
|
||||
def normalize_text(v):
|
||||
if v is None:
|
||||
return None
|
||||
s = str(v).strip()
|
||||
return s if s != "" else None
|
||||
|
||||
def import_df(df: pd.DataFrame, source_file: str):
|
||||
required = ["名称(必填)", "进货价(必填)", "销售价(必填)", "条码"]
|
||||
for col in required:
|
||||
if col not in df.columns:
|
||||
return {"error": f"missing column: {col}"}
|
||||
df = df[["名称(必填)", "进货价(必填)", "销售价(必填)", "条码", "分类(必填)"]].copy() if "分类(必填)" in df.columns else df[["名称(必填)", "进货价(必填)", "销售价(必填)", "条码"]].copy()
|
||||
df.columns = ["name", "purchase_price", "sale_price", "barcode"] + (["category"] if df.shape[1] == 5 else [])
|
||||
df["name"] = df["name"].apply(normalize_text)
|
||||
df["barcode"] = df["barcode"].apply(normalize_text)
|
||||
df["purchase_price"] = df["purchase_price"].apply(normalize_price)
|
||||
df["sale_price"] = df["sale_price"].apply(normalize_price)
|
||||
if "category" in df.columns:
|
||||
df["category"] = df["category"].apply(normalize_text)
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
inserted = 0
|
||||
updated = 0
|
||||
skipped = 0
|
||||
conn = get_conn()
|
||||
cur = conn.cursor()
|
||||
for _, row in df.iterrows():
|
||||
bc = row.get("barcode")
|
||||
nm = row.get("name")
|
||||
pp = row.get("purchase_price")
|
||||
sp = row.get("sale_price")
|
||||
ct = row.get("category") if "category" in df.columns else None
|
||||
if bc is None:
|
||||
skipped += 1
|
||||
continue
|
||||
cur.execute("select barcode from products where barcode=?", (bc,))
|
||||
exists = cur.fetchone()
|
||||
if exists:
|
||||
cur.execute(
|
||||
"update products set name=?, purchase_price=?, sale_price=?, category=?, created_at=?, source_file=? where barcode=?",
|
||||
(nm, pp, sp, ct, now, source_file, bc),
|
||||
)
|
||||
updated += 1
|
||||
else:
|
||||
cur.execute(
|
||||
"insert into products(barcode,name,purchase_price,sale_price,category,created_at,source_file) values(?,?,?,?,?,?,?)",
|
||||
(bc, nm, pp, sp, ct, now, source_file),
|
||||
)
|
||||
inserted += 1
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"inserted": inserted, "updated": updated, "skipped": skipped}
|
||||
|
||||
@app.post("/import")
|
||||
async def import_excel(file: UploadFile = File(...)):
|
||||
tmp = os.path.join(os.getcwd(), "data", f"upload_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{file.filename}")
|
||||
with open(tmp, "wb") as f:
|
||||
f.write(await file.read())
|
||||
try:
|
||||
df = pd.read_excel(tmp, sheet_name="Sheet1")
|
||||
result = import_df(df, os.path.basename(file.filename))
|
||||
return result if isinstance(result, dict) else {"error": "import failed"}
|
||||
finally:
|
||||
pass
|
||||
|
||||
@app.get("/products/{barcode}")
|
||||
def get_product(barcode: str):
|
||||
conn = get_conn()
|
||||
cur = conn.cursor()
|
||||
cur.execute("select name,purchase_price,sale_price,barcode from products where barcode=?", (barcode,))
|
||||
row = cur.fetchone()
|
||||
conn.close()
|
||||
if not row:
|
||||
return JSONResponse(status_code=404, content={"error": "not found"})
|
||||
return {"name": row[0], "purchase_price": row[1], "sale_price": row[2], "barcode": row[3]}
|
||||
|
||||
@app.get("/products")
|
||||
def search_products(q: str = Query("", min_length=0), limit: int = 20, page: int = 1, sort: str = "", order: str = "asc"):
|
||||
conn = get_conn()
|
||||
cur = conn.cursor()
|
||||
offset = max((page - 1), 0) * max(limit, 1)
|
||||
sort_map = {"name": "name", "sale_price": "sale_price", "purchase_price": "purchase_price", "created_at": "created_at", "barcode": "barcode"}
|
||||
sort_col = sort_map.get(sort, "")
|
||||
order_sql = "DESC" if str(order).lower() == "desc" else "ASC"
|
||||
if q:
|
||||
s = q.strip()
|
||||
if s.isdigit():
|
||||
if sort_col:
|
||||
cur.execute(
|
||||
f"select distinct name,purchase_price,sale_price,barcode from products where barcode like ? or barcode like ? or barcode like ? or name like ? order by {sort_col} {order_sql} limit ? offset ?",
|
||||
(f"{s}%", f"%{s}", f"%{s}%", f"%{s}%", limit, offset),
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
"select name,purchase_price,sale_price,barcode, case when barcode like ? then 0 when barcode like ? then 1 when barcode like ? then 2 when name like ? then 3 else 4 end as score from products where barcode like ? or barcode like ? or barcode like ? or name like ? order by score, name limit ? offset ?",
|
||||
(f"{s}%", f"%{s}", f"%{s}%", f"%{s}%", f"{s}%", f"%{s}", f"%{s}%", f"%{s}%", limit, offset),
|
||||
)
|
||||
else:
|
||||
if sort_col:
|
||||
cur.execute(
|
||||
f"select name,purchase_price,sale_price,barcode from products where name like ? order by {sort_col} {order_sql} limit ? offset ?",
|
||||
(f"%{s}%", limit, offset),
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
"select name,purchase_price,sale_price,barcode, case when instr(name, ?) = 1 then 0 when instr(name, ?) > 0 then 1 else 2 end as score from products where name like ? order by score, name limit ? offset ?",
|
||||
(s, s, f"%{s}%", limit, offset),
|
||||
)
|
||||
else:
|
||||
if sort_col:
|
||||
cur.execute(f"select name,purchase_price,sale_price,barcode from products order by {sort_col} {order_sql} limit ? offset ?", (limit, offset))
|
||||
else:
|
||||
cur.execute("select name,purchase_price,sale_price,barcode from products order by name limit ? offset ?", (limit, offset))
|
||||
rows = cur.fetchall()
|
||||
conn.close()
|
||||
return [{"name": r[0], "purchase_price": r[1], "sale_price": r[2], "barcode": r[3]} for r in rows]
|
||||
Reference in New Issue
Block a user