From 0e1ee0820eab6f5e435f887557ad1cc53cc70f86 Mon Sep 17 00:00:00 2001 From: 91771 Date: Mon, 8 Sep 2025 16:23:54 +0800 Subject: [PATCH] Initial commit --- DB_connection.txt | 7 + README.md | 85 +++++ app.py | 701 +++++++++++++++++++++++++++++++++++++++++ create_pizzas_table.py | 191 +++++++++++ requirements.txt | 3 + sales_dashboard.html | 317 +++++++++++++++++++ 6 files changed, 1304 insertions(+) create mode 100644 DB_connection.txt create mode 100644 README.md create mode 100644 app.py create mode 100644 create_pizzas_table.py create mode 100644 requirements.txt create mode 100644 sales_dashboard.html diff --git a/DB_connection.txt b/DB_connection.txt new file mode 100644 index 0000000..8dd2d65 --- /dev/null +++ b/DB_connection.txt @@ -0,0 +1,7 @@ +資料庫資訊: +DB_HOST = mysql.theaken.com +DB_PORT = 33306 +DB_NAME = db_A019 +DB_USER = A019 +DB_PASSWORD = 9wvKEkxBzVca +pip install -r requirements.txt \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f276aa3 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# Flask API 伺服器 + +這是一個使用 Flask 和 MySQL 建立的 RESTful API 伺服器,提供使用者資料的 CRUD 操作。 + +## 功能 + +- 獲取使用者列表 (支援過濾和分頁) +- 獲取單一使用者資料 +- 新增使用者 +- 更新使用者資料 +- 刪除使用者 + +## API 端點 + +### GET /v1/users +獲取使用者列表,支援以下查詢參數: +- `min_age`: 最小年齡過濾 +- `max_age`: 最大年齡過濾 +- `page`: 頁碼 (預設: 1) +- `limit`: 每頁筆數 (預設: 10) + +### GET /v1/users/ +獲取指定 ID 的使用者資料 + +### POST /v1/users +新增使用者,需提供以下 JSON 資料: +```json +{ + "name": "使用者名稱", + "email": "使用者信箱", + "age": 使用者年齡 +} +``` + +### PATCH /v1/users/ +更新指定 ID 的使用者資料,可提供以下任一欄位: +```json +{ + "name": "新名稱", + "email": "新信箱", + "age": 新年齡 +} +``` + +### DELETE /v1/users/ +刪除指定 ID 的使用者 + +## 安裝與執行 + +1. 安裝相依套件: + ``` + pip install -r requirements.txt + ``` + +2. 執行伺服器: + ``` + python app.py + ``` + +伺服器將在 http://127.0.0.1:5000 啟動。 + +## 回應格式 + +所有 API 回應皆使用統一格式: + +```json +{ + "status": "success" 或 "error", + "code": HTTP 狀態碼, + "message": "回應訊息", + "data": 回應資料 (僅在成功時提供) +} +``` + +## 錯誤處理 + +錯誤回應格式: + +```json +{ + "status": "error", + "code": HTTP 錯誤碼, + "message": "錯誤訊息" +} +``` \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..51e47e6 --- /dev/null +++ b/app.py @@ -0,0 +1,701 @@ +from flask import Flask, request, jsonify +from flask_cors import CORS +import mysql.connector +import os +from datetime import datetime + +app = Flask(__name__) +CORS(app) # 啟用 CORS,允許本機前端存取 + +# 資料庫連線資訊 +DB_CONFIG = { + 'host': 'mysql.theaken.com', + 'port': 33306, + 'user': 'A019', + 'password': '9wvKEkxBzVca', + 'database': 'db_A019' +} + +# 建立資料庫連線 +def get_db_connection(): + try: + conn = mysql.connector.connect(**DB_CONFIG) + return conn + except mysql.connector.Error as err: + print(f"資料庫連線錯誤: {err}") + return None + +# 統一回應格式 +def create_response(status, code, message, data=None): + response = { + "status": status, + "code": code, + "message": message + } + if data is not None: + response["data"] = data + return response + +# 錯誤處理 +def create_error_response(code, message): + return create_response("error", code, message) + +# 獲取所有使用者,支援過濾和分頁 +@app.route('/v1/users', methods=['GET']) +def get_users(): + try: + # 取得查詢參數 + min_age = request.args.get('min_age', type=int) + max_age = request.args.get('max_age', type=int) + page = request.args.get('page', 1, type=int) + limit = request.args.get('limit', 10, type=int) + + # 計算偏移量 + offset = (page - 1) * limit + + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor(dictionary=True) + + # 建立基本查詢 + query = "SELECT * FROM users" + count_query = "SELECT COUNT(*) as total FROM users" + params = [] + where_clauses = [] + + # 加入過濾條件 + if min_age is not None: + where_clauses.append("age >= %s") + params.append(min_age) + + if max_age is not None: + where_clauses.append("age <= %s") + params.append(max_age) + + # 組合 WHERE 子句 + if where_clauses: + query += " WHERE " + " AND ".join(where_clauses) + count_query += " WHERE " + " AND ".join(where_clauses) + + # 加入分頁 + query += " LIMIT %s OFFSET %s" + params.extend([limit, offset]) + + # 執行查詢 + cursor.execute(query, params) + users = cursor.fetchall() + + # 獲取總記錄數 + cursor.execute(count_query, params[:-2] if params else []) + total = cursor.fetchone()['total'] + + # 計算總頁數 + total_pages = (total + limit - 1) // limit + + # 建立 meta 資訊 + meta = { + "total": total, + "page": page, + "limit": limit, + "total_pages": total_pages + } + + cursor.close() + conn.close() + + return jsonify(create_response("success", 200, "成功獲取使用者列表", {"users": users, "meta": meta})), 200 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 獲取所有披薩,支援過濾和分頁 +@app.route('/v1/pizzas', methods=['GET']) +def get_pizzas(): + try: + # 取得查詢參數 + min_price = request.args.get('min_price', type=float) + max_price = request.args.get('max_price', type=float) + size = request.args.get('size') + page = request.args.get('page', 1, type=int) + limit = request.args.get('limit', 10, type=int) + + # 計算偏移量 + offset = (page - 1) * limit + + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor(dictionary=True) + + # 建立基本查詢 + query = "SELECT * FROM pizzas" + count_query = "SELECT COUNT(*) as total FROM pizzas" + params = [] + where_clauses = [] + + # 加入過濾條件 + if min_price is not None: + where_clauses.append("price >= %s") + params.append(min_price) + + if max_price is not None: + where_clauses.append("price <= %s") + params.append(max_price) + + if size is not None: + where_clauses.append("size = %s") + params.append(size) + + # 組合 WHERE 子句 + if where_clauses: + query += " WHERE " + " AND ".join(where_clauses) + count_query += " WHERE " + " AND ".join(where_clauses) + + # 加入分頁 + query += " LIMIT %s OFFSET %s" + params.extend([limit, offset]) + + # 執行查詢 + cursor.execute(query, params) + pizzas = cursor.fetchall() + + # 獲取總記錄數 + cursor.execute(count_query, params[:-2] if params else []) + total = cursor.fetchone()['total'] + + # 計算總頁數 + total_pages = (total + limit - 1) // limit + + # 建立 meta 資訊 + meta = { + "total": total, + "page": page, + "limit": limit, + "total_pages": total_pages + } + + cursor.close() + conn.close() + + return jsonify(create_response("success", 200, "成功獲取披薩列表", {"pizzas": pizzas, "meta": meta})), 200 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 獲取單一使用者 +@app.route('/v1/users/', methods=['GET']) +def get_user(user_id): + try: + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor(dictionary=True) + cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + user = cursor.fetchone() + + cursor.close() + conn.close() + + if not user: + return jsonify(create_error_response(404, f"找不到 ID 為 {user_id} 的使用者")), 404 + + return jsonify(create_response("success", 200, "成功獲取使用者資料", {"user": user})), 200 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 獲取單一披薩 +@app.route('/v1/pizzas/', methods=['GET']) +def get_pizza_by_id(pizza_id): + try: + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor(dictionary=True) + cursor.execute("SELECT * FROM pizzas WHERE id = %s", (pizza_id,)) + pizza = cursor.fetchone() + + cursor.close() + conn.close() + + if not pizza: + return jsonify(create_error_response(404, f"找不到 ID 為 {pizza_id} 的披薩")), 404 + + return jsonify(create_response("success", 200, "成功獲取披薩資料", {"pizza": pizza})), 200 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + user = cursor.fetchone() + + cursor.close() + conn.close() + + if not user: + return jsonify(create_error_response(404, f"找不到 ID 為 {user_id} 的使用者")), 404 + + return jsonify(create_response("success", 200, "成功獲取使用者資料", {"user": user})), 200 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 獲取單一披薩 +# 已在上方定義 GET /v1/pizzas/ 路由 + +# 新增披薩 +@app.route('/v1/pizzas', methods=['POST']) +def create_pizza(): + try: + data = request.get_json() + + # 驗證必要欄位 + required_fields = ['name', 'price', 'size'] + for field in required_fields: + if field not in data: + return jsonify(create_error_response(400, f"缺少必要欄位: {field}")), 400 + + # 驗證資料類型 + if not isinstance(data['name'], str): + return jsonify(create_error_response(400, "name 必須是字串")), 400 + + try: + price = float(data['price']) + if price <= 0: + return jsonify(create_error_response(400, "price 必須大於 0")), 400 + except (ValueError, TypeError): + return jsonify(create_error_response(400, "price 必須是有效的數字")), 400 + + if not isinstance(data['size'], str) or data['size'] not in ['S', 'M', 'L']: + return jsonify(create_error_response(400, "size 必須是 S、M 或 L")), 400 + + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor(dictionary=True) + + # 設定時間 + now = datetime.now() + + # 插入資料 + insert_query = "INSERT INTO pizzas (name, price, size, created_at, updated_at) VALUES (%s, %s, %s, %s, %s)" + cursor.execute(insert_query, (data['name'], price, data['size'], now, now)) + pizza_id = cursor.lastrowid + conn.commit() + + # 獲取新增的披薩資料 + cursor.execute("SELECT * FROM pizzas WHERE id = %s", (pizza_id,)) + new_pizza = cursor.fetchone() + + cursor.close() + conn.close() + + return jsonify(create_response("success", 201, "成功新增披薩", {"pizza": new_pizza})), 201 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 更新披薩 +@app.route('/v1/pizzas/', methods=['PATCH']) +def update_pizza(pizza_id): + try: + data = request.get_json() + + # 至少需要一個欄位 + if not data or not any(field in data for field in ['name', 'price', 'size']): + return jsonify(create_error_response(400, "至少需要提供一個欄位: name, price, size")), 400 + + # 驗證資料類型 + if 'name' in data and not isinstance(data['name'], str): + return jsonify(create_error_response(400, "name 必須是字串")), 400 + + if 'price' in data: + try: + price = float(data['price']) + if price <= 0: + return jsonify(create_error_response(400, "price 必須大於 0")), 400 + data['price'] = price + except (ValueError, TypeError): + return jsonify(create_error_response(400, "price 必須是有效的數字")), 400 + + if 'size' in data and (not isinstance(data['size'], str) or data['size'] not in ['S', 'M', 'L']): + return jsonify(create_error_response(400, "size 必須是 S、M 或 L")), 400 + + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor(dictionary=True) + + # 檢查披薩是否存在 + cursor.execute("SELECT * FROM pizzas WHERE id = %s", (pizza_id,)) + pizza = cursor.fetchone() + + if not pizza: + cursor.close() + conn.close() + return jsonify(create_error_response(404, f"找不到 ID 為 {pizza_id} 的披薩")), 404 + + # 設定更新時間 + now = datetime.now() + data['updated_at'] = now + + # 建立更新查詢 + update_fields = [] + update_values = [] + + for field in ['name', 'price', 'size', 'updated_at']: + if field in data: + update_fields.append(f"{field} = %s") + update_values.append(data[field]) + + update_values.append(pizza_id) # WHERE id = %s 的參數 + + update_query = f"UPDATE pizzas SET {', '.join(update_fields)} WHERE id = %s" + cursor.execute(update_query, update_values) + conn.commit() + + # 獲取更新後的披薩資料 + cursor.execute("SELECT * FROM pizzas WHERE id = %s", (pizza_id,)) + updated_pizza = cursor.fetchone() + + cursor.close() + conn.close() + + return jsonify(create_response("success", 200, "成功更新披薩", {"pizza": updated_pizza})), 200 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 刪除披薩 +@app.route('/v1/pizzas/', methods=['DELETE']) +def delete_pizza(pizza_id): + try: + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor() + + # 檢查披薩是否存在 + cursor.execute("SELECT id FROM pizzas WHERE id = %s", (pizza_id,)) + pizza = cursor.fetchone() + + if not pizza: + cursor.close() + conn.close() + return jsonify(create_error_response(404, f"找不到 ID 為 {pizza_id} 的披薩")), 404 + + # 刪除披薩 + cursor.execute("DELETE FROM pizzas WHERE id = %s", (pizza_id,)) + conn.commit() + + cursor.close() + conn.close() + + return jsonify(create_response("success", 204, "成功刪除披薩")), 204 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 新增使用者 +@app.route('/v1/users', methods=['POST']) +def create_user(): + try: + data = request.get_json() + + # 驗證必要欄位 + required_fields = ['name', 'email', 'age'] + for field in required_fields: + if field not in data: + return jsonify(create_error_response(400, f"缺少必要欄位: {field}")), 400 + + # 驗證資料類型 + if not isinstance(data['name'], str): + return jsonify(create_error_response(400, "name 必須是字串")), 400 + + # 驗證資料類型 + if not isinstance(data['name'], str): + return jsonify(create_error_response(400, "name 必須是字串")), 400 + + try: + price = float(data['price']) + if price <= 0: + return jsonify(create_error_response(400, "price 必須大於 0")), 400 + except (ValueError, TypeError): + return jsonify(create_error_response(400, "price 必須是有效的數字")), 400 + + if not isinstance(data['size'], str) or data['size'] not in ['S', 'M', 'L']: + return jsonify(create_error_response(400, "size 必須是 S、M 或 L")), 400 + + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor() + + # 設定時間 + now = datetime.now() + + # 插入資料 + insert_query = "INSERT INTO pizzas (name, price, size, created_at, updated_at) VALUES (%s, %s, %s, %s, %s)" + cursor.execute(insert_query, (data['name'], price, data['size'], now, now)) + pizza_id = cursor.lastrowid + conn.commit() + + # 獲取新增的披薩資料 + cursor.execute("SELECT * FROM pizzas WHERE id = %s", (pizza_id,)) + new_pizza = cursor.fetchone() + + cursor.close() + conn.close() + + return jsonify(create_response("success", 201, "成功新增披薩", {"pizza": new_pizza})), 201 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + + if not isinstance(data['email'], str): + return jsonify(create_error_response(400, "email 必須是字串")), 400 + + if not isinstance(data['age'], int): + return jsonify(create_error_response(400, "age 必須是整數")), 400 + + # 驗證 email 格式 (簡單驗證) + if '@' not in data['email']: + return jsonify(create_error_response(400, "email 格式不正確")), 400 + + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor(dictionary=True) + + # 使用參數化查詢避免 SQL 注入 + query = "INSERT INTO users (name, email, age) VALUES (%s, %s, %s)" + cursor.execute(query, (data['name'], data['email'], data['age'])) + + # 獲取新增的使用者 ID + user_id = cursor.lastrowid + + conn.commit() + + # 獲取新增的使用者資料 + cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + new_user = cursor.fetchone() + + cursor.close() + conn.close() + + return jsonify(create_response("success", 201, "成功新增使用者", {"user": new_user})), 201 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 更新使用者 +@app.route('/v1/users/', methods=['PATCH']) +def update_user(user_id): + try: + data = request.get_json() + + # 檢查是否有要更新的欄位 + if not data: + return jsonify(create_error_response(400, "沒有提供要更新的資料")), 400 + + # 驗證資料類型 + if 'name' in data and not isinstance(data['name'], str): + return jsonify(create_error_response(400, "name 必須是字串")), 400 + + if 'email' in data and not isinstance(data['email'], str): + return jsonify(create_error_response(400, "email 必須是字串")), 400 + + if 'age' in data and not isinstance(data['age'], int): + return jsonify(create_error_response(400, "age 必須是整數")), 400 + + # 驗證 email 格式 (簡單驗證) + if 'email' in data and '@' not in data['email']: + return jsonify(create_error_response(400, "email 格式不正確")), 400 + + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor(dictionary=True) + + # 檢查使用者是否存在 + cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + user = cursor.fetchone() + + if not user: + cursor.close() + conn.close() + return jsonify(create_error_response(404, f"找不到 ID 為 {user_id} 的使用者")), 404 + + # 建立更新查詢 + update_fields = [] + params = [] + + if 'name' in data: + update_fields.append("name = %s") + params.append(data['name']) + + if 'email' in data: + update_fields.append("email = %s") + params.append(data['email']) + + if 'age' in data: + update_fields.append("age = %s") + params.append(data['age']) + + # 如果沒有要更新的欄位 + if not update_fields: + cursor.close() + conn.close() + return jsonify(create_error_response(400, "沒有提供有效的更新欄位")), 400 + + # 建立更新查詢 + query = "UPDATE users SET " + ", ".join(update_fields) + " WHERE id = %s" + params.append(user_id) + + # 執行更新 + cursor.execute(query, params) + conn.commit() + + # 獲取更新後的使用者資料 + cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + updated_user = cursor.fetchone() + + cursor.close() + conn.close() + + return jsonify(create_response("success", 200, "成功更新使用者資料", {"user": updated_user})), 200 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 刪除使用者 +@app.route('/v1/users/', methods=['DELETE']) +def delete_user(user_id): + try: + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor() + + # 檢查使用者是否存在 + cursor.execute("SELECT id FROM users WHERE id = %s", (user_id,)) + user = cursor.fetchone() + + if not user: + cursor.close() + conn.close() + return jsonify(create_error_response(404, f"找不到 ID 為 {user_id} 的使用者")), 404 + + # 刪除使用者 + cursor.execute("DELETE FROM users WHERE id = %s", (user_id,)) + conn.commit() + + cursor.close() + conn.close() + + return jsonify(create_response("success", 204, "成功刪除使用者")), 204 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 獲取所有 pizzas,支援分頁 +# 已在上方定義 GET /v1/pizzas 路由 + + # 獲取總記錄數 + cursor.execute(count_query, params[:-2] if params else []) + total = cursor.fetchone()['total'] + + # 計算總頁數 + total_pages = (total + limit - 1) // limit + + # 建立 meta 資訊 + meta = { + "total": total, + "page": page, + "limit": limit, + "total_pages": total_pages + } + + cursor.close() + conn.close() + + return jsonify(create_response("success", 200, "成功獲取披薩列表", {"pizzas": pizzas, "meta": meta})), 200 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 獲取單一 pizza +# 注意:此路由已在其他地方定義 +def get_pizza(pizza_id): + try: + conn = get_db_connection() + if not conn: + return jsonify(create_error_response(500, "資料庫連線失敗")), 500 + + cursor = conn.cursor(dictionary=True) + cursor.execute("SELECT * FROM pizzas WHERE id = %s", (pizza_id,)) + pizza = cursor.fetchone() + + cursor.close() + conn.close() + + if not pizza: + return jsonify(create_error_response(404, f"找不到 ID 為 {pizza_id} 的披薩")), 404 + + return jsonify(create_response("success", 200, "成功獲取披薩資料", {"pizza": pizza})), 200 + + except Exception as e: + return jsonify(create_error_response(500, f"伺服器錯誤: {str(e)}")), 500 + +# 新增 pizza +# 已在上方定義 POST /v1/pizzas 路由 + +# 更新 pizza +# 已在上方定義 PATCH /v1/pizzas/ 路由 + +# 刪除 pizza +# 已在上方定義 DELETE /v1/pizzas/ 路由 + +# 主程式入口 +if __name__ == '__main__': + # 確保資料表存在 + conn = get_db_connection() + if conn: + cursor = conn.cursor() + cursor.execute(""" + CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + email VARCHAR(100) NOT NULL, + age INT NOT NULL + ) + """) + + # 確保 pizzas 資料表存在 + cursor.execute(""" + CREATE TABLE IF NOT EXISTS pizzas ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50) NOT NULL, + price DECIMAL(10, 2) NOT NULL, + size VARCHAR(2) NOT NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL + ) + """) + + conn.commit() + cursor.close() + conn.close() + + app.run(debug=True) \ No newline at end of file diff --git a/create_pizzas_table.py b/create_pizzas_table.py new file mode 100644 index 0000000..f6106f0 --- /dev/null +++ b/create_pizzas_table.py @@ -0,0 +1,191 @@ +import mysql.connector +import random +from datetime import datetime + +# 資料庫連線資訊 +DB_CONFIG = { + 'host': 'mysql.theaken.com', + 'port': 33306, + 'user': 'A019', + 'password': '9wvKEkxBzVca', + 'database': 'db_A019' +} + +# 建立資料庫連線 +def get_db_connection(): + try: + conn = mysql.connector.connect(**DB_CONFIG) + return conn + except mysql.connector.Error as err: + print(f"資料庫連線錯誤: {err}") + return None + +# 建立 pizzas 資料表 +def create_pizzas_table(): + conn = get_db_connection() + if not conn: + print("無法連接到資料庫") + return False + + cursor = conn.cursor() + + # 建立 pizzas 資料表 + create_table_query = """ + CREATE TABLE IF NOT EXISTS pizzas ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50) NOT NULL, + price DECIMAL(10, 2) NOT NULL, + size VARCHAR(2) NOT NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL + ) + """ + + try: + cursor.execute(create_table_query) + conn.commit() + print("成功建立 pizzas 資料表") + return True + except mysql.connector.Error as err: + print(f"建立資料表錯誤: {err}") + return False + finally: + cursor.close() + conn.close() + +# 產生隨機 pizza 資料 +def generate_random_pizzas(count=6): + # Pizza 名稱列表 + pizza_names = [ + "夏威夷披薩", "瑪格麗特披薩", "蘑菇披薩", "臘腸披薩", + "海鮮披薩", "素食披薩", "四季披薩", "墨西哥辣味披薩", + "起司披薩", "燒烤雞肉披薩", "牛肉披薩", "蔬菜披薩" + ] + + # 確保名稱不重複,如果 count 大於名稱列表長度,則使用所有可用名稱 + if count > len(pizza_names): + count = len(pizza_names) + + # 隨機選擇不重複的名稱 + selected_names = random.sample(pizza_names, count) + + # Pizza 尺寸 + pizza_sizes = ["S", "M", "L"] + + # 隨機生成 pizza 資料 + pizzas = [] + for name in selected_names: + size = random.choice(pizza_sizes) + + # 根據尺寸設定價格範圍 + if size == "S": + price = round(random.uniform(200, 300), 2) + elif size == "M": + price = round(random.uniform(300, 400), 2) + else: # L + price = round(random.uniform(400, 500), 2) + + # 設定時間 + now = datetime.now() + created_at = now + updated_at = now + + pizzas.append((name, price, size, created_at, updated_at)) + + return pizzas + +# 插入隨機 pizza 資料 +def insert_random_pizzas(count=6): + conn = get_db_connection() + if not conn: + print("無法連接到資料庫") + return False + + cursor = conn.cursor() + + # 生成隨機 pizza 資料 + pizzas = generate_random_pizzas(count) + + # 插入資料 + insert_query = """ + INSERT INTO pizzas (name, price, size, created_at, updated_at) + VALUES (%s, %s, %s, %s, %s) + """ + + try: + cursor.executemany(insert_query, pizzas) + conn.commit() + print(f"成功插入 {cursor.rowcount} 筆 pizza 資料") + return True + except mysql.connector.Error as err: + print(f"插入資料錯誤: {err}") + return False + finally: + cursor.close() + conn.close() + +# 顯示所有 pizza 資料 +def show_all_pizzas(): + conn = get_db_connection() + if not conn: + print("無法連接到資料庫") + return + + cursor = conn.cursor(dictionary=True) + + try: + cursor.execute("SELECT * FROM pizzas") + pizzas = cursor.fetchall() + + if not pizzas: + print("沒有找到任何 pizza 資料") + return + + print("\n所有 Pizza 資料:") + print("-" * 80) + print(f"{'ID':<5} {'名稱':<20} {'價格':<10} {'尺寸':<5} {'建立時間':<20} {'更新時間':<20}") + print("-" * 80) + + for pizza in pizzas: + print(f"{pizza['id']:<5} {pizza['name']:<20} {pizza['price']:<10} {pizza['size']:<5} {pizza['created_at']} {pizza['updated_at']}") + + except mysql.connector.Error as err: + print(f"查詢資料錯誤: {err}") + finally: + cursor.close() + conn.close() + +# 清空 pizzas 資料表 +def truncate_pizzas_table(): + conn = get_db_connection() + if not conn: + print("無法連接到資料庫") + return False + + cursor = conn.cursor() + + try: + cursor.execute("TRUNCATE TABLE pizzas") + conn.commit() + print("成功清空 pizzas 資料表") + return True + except mysql.connector.Error as err: + print(f"清空資料表錯誤: {err}") + return False + finally: + cursor.close() + conn.close() + +# 主程式 +def main(): + # 建立資料表 + if create_pizzas_table(): + # 清空資料表 + if truncate_pizzas_table(): + # 插入隨機資料 + if insert_random_pizzas(6): + # 顯示所有資料 + show_all_pizzas() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b993871 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask==2.3.3 +flask-cors==4.0.0 +mysql-connector-python==8.1.0 \ No newline at end of file diff --git a/sales_dashboard.html b/sales_dashboard.html new file mode 100644 index 0000000..c6a5f4b --- /dev/null +++ b/sales_dashboard.html @@ -0,0 +1,317 @@ + + + + + + + Sales Dashboard + + + + + +
+

Sales Dashboard

+ +
+
+

Total Sales

+

Loading...

+
+
+

Total Orders

+

Loading...

+
+
+

Product Categories

+

Loading...

+
+
+

Sales Regions

+

Loading...

+
+
+ +

Sales by Product Category

+
+ +
+ +

Sales Over Time

+
+ +
+ +

All Sales Data

+
+ + + + + + + + + +
+
+ +
+ + + + + \ No newline at end of file