一个手机快速搜索商品的网页

This commit is contained in:
2025-12-07 15:09:21 +08:00
commit fcbcdb7f95
17 changed files with 815 additions and 0 deletions
Binary file not shown.
+179
View File
@@ -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]