diff --git a/Project/__pycache__/config.cpython-311.pyc b/Project/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000..6b4d436 Binary files /dev/null and b/Project/__pycache__/config.cpython-311.pyc differ diff --git a/Project/__pycache__/main.cpython-311.pyc b/Project/__pycache__/main.cpython-311.pyc index 6843b95..48a1d33 100644 Binary files a/Project/__pycache__/main.cpython-311.pyc and b/Project/__pycache__/main.cpython-311.pyc differ diff --git a/Project/db.sql b/Project/db.sql index 9f284b4..50163fd 100644 --- a/Project/db.sql +++ b/Project/db.sql @@ -1,102 +1,58 @@ -DROP TABLE IF EXISTS Sold; -DROP TABLE IF EXISTS Transactions; -DROP TABLE IF EXISTS Passbys; DROP TABLE IF EXISTS Tickets; -DROP TABLE IF EXISTS Seats; +DROP TABLE IF EXISTS Flights; +DROP TABLE IF EXISTS Airports; DROP TABLE IF EXISTS Users; DROP TABLE IF EXISTS Passengers; -DROP TABLE IF EXISTS Rides; -DROP TABLE IF EXISTS Trains; -DROP TABLE IF EXISTS Stations; CREATE TABLE Passengers ( - ID BIGINT PRIMARY KEY, - `Name` VARCHAR ( 255 ) NOT NULL, - Phone_number BIGINT UNIQUE NOT NULL, - CHECK ( REGEXP_LIKE ( ID, '^\\d{18}$' ) ), - CHECK ( REGEXP_LIKE ( Phone_number, '^\\d{11}$' ) ) + ID BIGINT PRIMARY KEY, + `Name` VARCHAR(255) NOT NULL, + Phone_number BIGINT NOT NULL, + CHECK (REGEXP_LIKE(ID, '^\\d{18}$')), + CHECK (REGEXP_LIKE(Phone_number, '^\\d{11}$')) ); CREATE TABLE Users ( - ID INT AUTO_INCREMENT PRIMARY KEY, - `Password` VARCHAR ( 255 ) NOT NULL, - Phone_number BIGINT UNIQUE NOT NULL, - CitizenID BIGINT UNIQUE NOT NULL, - FOREIGN KEY ( Phone_number ) REFERENCES Passengers ( Phone_number ) ON DELETE CASCADE, - FOREIGN KEY ( CitizenID ) REFERENCES Passengers ( ID ) ON DELETE CASCADE + Phone_number BIGINT PRIMARY KEY, + Username VARCHAR(255) NOT NULL, + `Password` VARCHAR(255) NOT NULL, + CHECK (REGEXP_LIKE(Phone_number, '^\\d{11}$')) ); -CREATE TABLE Stations ( - ID INT AUTO_INCREMENT PRIMARY KEY, - `Name` VARCHAR ( 255 ) UNIQUE NOT NULL, - Province VARCHAR ( 255 ) NOT NULL, - City VARCHAR ( 255 ) NOT NULL +CREATE TABLE Airports ( + ID INT AUTO_INCREMENT PRIMARY KEY, + `Name` VARCHAR(255) UNIQUE NOT NULL, + Country VARCHAR(255) NOT NULL, + City VARCHAR(255) NOT NULL ); -CREATE TABLE Trains ( - ID VARCHAR ( 255 ) PRIMARY KEY, - TrainName VARCHAR ( 255 ) NOT NULL, - StationID_start INT NOT NULL, - StationID_arrive INT NOT NULL, - FOREIGN KEY ( StationID_start ) REFERENCES Stations ( ID ) ON DELETE CASCADE, - FOREIGN KEY ( StationID_arrive ) REFERENCES Stations ( ID ) ON DELETE CASCADE -); - -CREATE TABLE Seats ( - ID VARCHAR ( 255 ) PRIMARY KEY, - Type VARCHAR ( 255 ) NOT NULL, - TrainID VARCHAR ( 255 ) NOT NULL, - FOREIGN KEY ( TrainID ) REFERENCES Trains ( ID ) ON DELETE CASCADE +CREATE TABLE Flights ( + ID VARCHAR(255) PRIMARY KEY, + Airline VARCHAR(255) NOT NULL, + Departure_airport INT NOT NULL, + Arrival_airport INT NOT NULL, + Departure_time DATETIME NOT NULL, + Arrival_time DATETIME NOT NULL, + First_class_seats_remaining INT NOT NULL, -- 头等舱剩余座位 + Business_class_seats_remaining INT NOT NULL, -- 商务舱剩余座位 + Economy_class_seats_remaining INT NOT NULL, -- 经济舱剩余座位 + First_class_price DECIMAL(7, 2) NOT NULL, -- 头等舱价格 + Business_class_price DECIMAL(7, 2) NOT NULL, -- 商务舱价格 + Economy_class_price DECIMAL(7, 2) NOT NULL, -- 经济舱价格 + `Status` VARCHAR(255) NOT NULL CHECK (`Status` IN ('候机中', '延误', '已起飞', '已降落', '开始检票')), -- 航班状态 + FOREIGN KEY (Departure_airport) REFERENCES Airports(ID) ON DELETE CASCADE, + FOREIGN KEY (Arrival_airport) REFERENCES Airports(ID) ON DELETE CASCADE, + CHECK (Departure_time < Arrival_time) ); CREATE TABLE Tickets ( - ID INT AUTO_INCREMENT PRIMARY KEY, - Price DECIMAL ( 7, 2 ) NOT NULL, - Time_start DATETIME NOT NULL, - Time_arrive DATETIME NOT NULL, - StationID_start INT NOT NULL, - StationID_arrive INT NOT NULL, - SeatID VARCHAR ( 255 ) NOT NULL, - FOREIGN KEY ( StationID_start ) REFERENCES Stations ( ID ) ON DELETE CASCADE, - FOREIGN KEY ( StationID_arrive ) REFERENCES Stations ( ID ) ON DELETE CASCADE, - FOREIGN KEY ( SeatID ) REFERENCES Seats ( ID ) ON DELETE CASCADE, - CHECK ( Time_start <= Time_arrive ) -); - -CREATE TABLE Transactions ( - ID BIGINT AUTO_INCREMENT PRIMARY KEY, - `Time` DATETIME NOT NULL, - Paid TINYINT NOT NULL, - `User` INT NOT NULL, - PassengerID BIGINT NOT NULL, - TrainID VARCHAR ( 255 ) NOT NULL, - StationID_start INT NOT NULL, - StationID_arrive INT NOT NULL, - Time_start DATETIME NOT NULL, - Time_arrive DATETIME NOT NULL, - SeatID VARCHAR ( 255 ) NOT NULL, - FOREIGN KEY ( `User` ) REFERENCES Users ( ID ) ON DELETE CASCADE, - FOREIGN KEY ( PassengerID ) REFERENCES Passengers ( ID ) ON DELETE CASCADE, - FOREIGN KEY ( TrainID ) REFERENCES Trains ( ID ) ON DELETE CASCADE, - FOREIGN KEY ( StationID_start ) REFERENCES Stations ( ID ) ON DELETE CASCADE, - FOREIGN KEY ( StationID_arrive ) REFERENCES Stations ( ID ) ON DELETE CASCADE, - FOREIGN KEY ( SeatID ) REFERENCES Seats ( ID ) ON DELETE CASCADE, - CHECK ( Time_start < Time_arrive ) -); - -CREATE TABLE Sold ( - TransactionID BIGINT NOT NULL, - TicketID INT NOT NULL, - FOREIGN KEY ( TransactionID ) REFERENCES Transactions ( ID ) ON DELETE CASCADE, - FOREIGN KEY ( TicketID ) REFERENCES Tickets ( ID ) ON DELETE CASCADE -); - -CREATE TABLE Passbys ( - StationID INT NOT NULL, - TrainID VARCHAR ( 255 ) NOT NULL, - arrive_time DATETIME NOT NULL, - leave_time DATETIME NOT NULL, - FOREIGN KEY ( StationID ) REFERENCES Stations ( ID ) ON DELETE CASCADE, - FOREIGN KEY ( TrainID ) REFERENCES Trains ( ID ) ON DELETE CASCADE, - CHECK ( arrive_time <= leave_time ) + ID INT AUTO_INCREMENT PRIMARY KEY, + Price DECIMAL(7, 2) NOT NULL, + FlightID VARCHAR(255) NOT NULL, + Seat_class VARCHAR(255) NOT NULL, -- 级别信息 + PassengerID BIGINT NOT NULL, + Paid TINYINT NOT NULL, + FOREIGN KEY (FlightID) REFERENCES Flights(ID) ON DELETE CASCADE, + FOREIGN KEY (PassengerID) REFERENCES Passengers(ID) ON DELETE CASCADE, + CHECK (Seat_class IN ('First Class', 'Business Class', 'Economy Class')) ); \ No newline at end of file diff --git a/Project/func/__pycache__/config.cpython-311.pyc b/Project/func/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000..2bfd9d7 Binary files /dev/null and b/Project/func/__pycache__/config.cpython-311.pyc differ diff --git a/Project/func/__pycache__/index.cpython-311.pyc b/Project/func/__pycache__/index.cpython-311.pyc index 7cabdea..72d8f8a 100644 Binary files a/Project/func/__pycache__/index.cpython-311.pyc and b/Project/func/__pycache__/index.cpython-311.pyc differ diff --git a/Project/func/__pycache__/login.cpython-311.pyc b/Project/func/__pycache__/login.cpython-311.pyc new file mode 100644 index 0000000..5b86723 Binary files /dev/null and b/Project/func/__pycache__/login.cpython-311.pyc differ diff --git a/Project/func/__pycache__/modify.cpython-311.pyc b/Project/func/__pycache__/modify.cpython-311.pyc index ef647e5..0e4db81 100644 Binary files a/Project/func/__pycache__/modify.cpython-311.pyc and b/Project/func/__pycache__/modify.cpython-311.pyc differ diff --git a/Project/func/__pycache__/signup.cpython-311.pyc b/Project/func/__pycache__/signup.cpython-311.pyc index 0ca3dfe..a42224e 100644 Binary files a/Project/func/__pycache__/signup.cpython-311.pyc and b/Project/func/__pycache__/signup.cpython-311.pyc differ diff --git a/Project/func/config.py b/Project/func/config.py new file mode 100644 index 0000000..8e3c745 --- /dev/null +++ b/Project/func/config.py @@ -0,0 +1,18 @@ +db = { + 'host':'localhost', + 'user':'kejingfan', + 'password':'KJF2811879', + 'database':'TESTDB' +} + +SECRET_KEY = 'ILOVEDATABASETECH' + +slideshow_images = [ + {"link": "https://www.csair.com/mcms/mcms/SG/zh/2024/20240208_6/index-zh.html?lang=zh&country=sg&utm_campaign=2402gwstu&utm_source=gw&utm_channel=sg-lb", "src": "https://www.csair.com/mcms/20240321/3ee85acd463f481bb33f0d535a5814c6.jpg"}, + {"link": "https://www.csair.com/mcms/mcms/SG/zh/2024/20240605_10/index_sg_cn.html?lang=zh&country=sg&utm_source=sg&utm_campaign=ZB001lydc&utm_channel=gw", "src": "https://www.csair.com/mcms/20240605/a553252769834188b0c76a9698292f27.jpg"}, + {"link": "https://www.csair.com/mcms/mcms/SG/zh/2024/20240426_17/index_cn.html?lang=zh&country=sg&utm_source=sg&utm_campaign=ZB001znjp&utm_channel=gw", "src": "https://www.csair.com/mcms/20240321/820cd99c111849408b84c2b579086ef6.jpg"}, + {"link": "https://www.csair.com/mcms/mcms/SG/zh/2024/20240524_2/index_cn.html?lang=zh&country=sg&country=my&utm_source=us&utm_campaign=ZB001nhzgx&utm_channel=gw", "src": "https://www.csair.com/mcms/20240321/61889331ca174670babd144bb064d398.jpg"}, + {"link": "https://www.csair.com/mcms/mcms/SG/zh/2024/20240514_7/coupon.html?lang=zh&country=sg", "src": "https://www.csair.com/mcms/20240321/97e67c05291b4c64a8905e8a0c915d89.jpg"}, + {"link": "#", "src": "https://www.csair.com/mcms/1026/43124f5d5124487f8d6678745ae42f57.jpg"}, + {"link": "https://www.csair.com/mcms/mcms/SG/zh/2023/20231120_2/index_sg_cn.html?lang=zh&country=sg&utm_source=sg&utm_campaign=ZB001mq&utm_channel=gw", "src": "https://www.csair.com/mcms/1026/ea00e9cb9d9b43bea6497a6895c6d9e1.jpg"} +] diff --git a/Project/func/index.py b/Project/func/index.py index 64f8a9a..336daa4 100644 --- a/Project/func/index.py +++ b/Project/func/index.py @@ -1,4 +1,15 @@ -from flask import render_template +from flask import render_template, request, g, redirect, url_for, session +from .config import slideshow_images def index(): - return render_template("index.html") \ No newline at end of file + if request.method == 'GET': + if not g.user: + return redirect(url_for("login")) + username = g.name + images = slideshow_images + return render_template('index.html', images=images, username=username) + +def logout(): + session.clear() + session.pop('user_id', None) + return redirect(url_for('login')) diff --git a/Project/func/login.py b/Project/func/login.py new file mode 100644 index 0000000..4600888 --- /dev/null +++ b/Project/func/login.py @@ -0,0 +1,35 @@ +from flask import request, jsonify, session, url_for, render_template +from .config import db, slideshow_images +import pymysql + + +def connect(mobileNo, encrypted_password): + conn = pymysql.connect(**db) + cursor = conn.cursor(pymysql.cursors.DictCursor) + args = (mobileNo, encrypted_password) + verify_sql = "SELECT Phone_number FROM Users WHERE Phone_number = %s AND `Password` = %s;" + cursor.execute(verify_sql, args) + user = cursor.fetchone() + cursor.close() + conn.close() + return user + +def login(): + if request.method == 'GET': + images = slideshow_images + return render_template('login.html', images=images) + + if request.method == 'POST': + session.pop('user_id', None) + mobileNo = request.json.get('username') + encrypted_password = request.json.get('password') + try: + user = connect(mobileNo, encrypted_password) + if not user: + return jsonify({'message': '用户不存在,请点击注册按钮注册'}), 401 + session['user_id'] = mobileNo + session.modified = True + return jsonify({'redirect': url_for('index')}) + except Exception as e: + print(e) + return jsonify({'message': '数据库错误,请稍后再试'}), 500 diff --git a/Project/func/modify.py b/Project/func/modify.py index 474a028..ea628cb 100644 --- a/Project/func/modify.py +++ b/Project/func/modify.py @@ -1,79 +1,127 @@ -from flask import render_template, request, flash, redirect, url_for +from flask import render_template, request, flash, redirect, url_for, session from typing import Dict - +from pymysql.cursors import Cursor from .get_db import get_db -from .verify_user import verify_user +def get_current_user(cursor: Cursor, phone_number: str): + sql = "SELECT Username FROM Users WHERE Phone_number = %s" + cursor.execute(sql, (phone_number,)) + return cursor.fetchone() + +def verify_user(cursor: Cursor, phone_number: str, password: str) -> str: + sql = """ + SELECT Password FROM Users WHERE Phone_number = %s; + """ + cursor.execute(sql, (phone_number,)) + record = cursor.fetchone() + if not record: + return "NO_USER" + if record[0] != password: + return "WRONG_PASSWORD" + return "USER_VERIFIED" class ModifyInfo: - def __init__(self, form:Dict[str, str]): - self.id = form['cardCode'] + def __init__(self, form: Dict[str, str], user_phone: str): + self.phone_number = user_phone + print(form) modifyType = form['modifyType'] - self.new_password = form['encryptedNewPassword'] - self.phone_number = form['mobileNo'] + self.new_password = form.get('encryptedNewPassword', None) + self.new_phone_number = form.get('mobileNo', None) + self.new_username = form.get('username', None) modifyType2command = { - '1':'delete account', - '2':'modify Password', - '3':'modify Phone_Number' + '删除账户': 'delete account', + '修改密码': 'modify Password', + '修改手机号': 'modify Phone_Number', + '修改用户名': 'modify Username' } self.sql_dict = { - 'delete account': 'DELETE FROM passengers WHERE ID = %s;', - 'modify Password': 'UPDATE passengers SET `Password` = %s WHERE ID = %s;', - 'modify Phone_Number': 'UPDATE passengers SET Phone_number = %s WHERE ID = %s;' + 'delete account': 'DELETE FROM Users WHERE Phone_number = %s;', + 'modify Password': 'UPDATE Users SET Password = %s WHERE Phone_number = %s;', + 'modify Phone_Number': 'UPDATE Users SET Phone_number = %s WHERE Phone_number = %s;', + 'modify Username': 'UPDATE Users SET Username = %s WHERE Phone_number = %s;' } self.sql_args_dict = { - 'delete account': (self.id,), - 'modify Password': (self.new_password, self.id), - 'modify Phone_Number': (self.phone_number, self.id) + 'delete account': (self.phone_number,), + 'modify Password': (self.new_password, self.phone_number), + 'modify Phone_Number': (self.new_phone_number, self.phone_number), + 'modify Username': (self.new_username, self.phone_number) } self.ok_message_dict = { 'delete account': "删除账户成功", 'modify Password': "修改密码成功", - 'modify Phone_Number': "修改手机号成功" + 'modify Phone_Number': "修改手机号成功", + 'modify Username': "修改用户名成功" } self.fail_message_dict = { 'delete account': "数据库异常,删除账户失败", 'modify Password': "数据库异常,修改密码失败", - 'modify Phone_Number': "数据库异常,修改手机号失败" + 'modify Phone_Number': "数据库异常,修改手机号失败", + 'modify Username': "数据库异常,修改用户名失败" } self.command = modifyType2command[modifyType] + def get_sql(self): return self.sql_dict[self.command] + def get_args(self): return self.sql_args_dict[self.command] + def get_ok_message(self): return self.ok_message_dict[self.command] + def get_fail_message(self): return self.fail_message_dict[self.command] def modify(): if request.method == 'GET': - return render_template('modify.html') - + user_phone = session.get('user_id') + db = get_db() + cursor = db.cursor() + current_user = get_current_user(cursor, user_phone) + if not current_user: + session.clear() + return redirect(url_for('login')) + return render_template('modify.html', current_user_phone=user_phone, current_username=current_user[0]) + if request.method == 'POST': - id = request.form['cardCode'] + user_phone = session.get('user_id') password = request.form['encryptedPassword'] db = get_db() cursor = db.cursor() - verify_info = verify_user(cursor, id, password) - if (verify_info == "NO_USER"): - flash("您未注册过,无法修改账号") - db.close() - return redirect(url_for('signup')) - elif (verify_info == "WRONG_PASSWORD"): + verify_info = verify_user(cursor, user_phone, password) + if verify_info == "NO_USER": + session.clear() + return redirect(url_for('login')) + elif verify_info == "WRONG_PASSWORD": flash("密码错误") db.close() return redirect(url_for('modify')) - modifyInfo = ModifyInfo(request.form) + modifyInfo = ModifyInfo(request.form, user_phone) + + if modifyInfo.command == 'modify Phone_Number': + check_sql = "SELECT COUNT(*) FROM Users WHERE Phone_number = %s;" + cursor.execute(check_sql, (modifyInfo.new_phone_number,)) + if cursor.fetchone()[0] > 0: + flash("手机号已存在,请使用其他手机号") + db.close() + return redirect(url_for('modify')) + try: cursor.execute(modifyInfo.get_sql(), modifyInfo.get_args()) db.commit() flash(modifyInfo.get_ok_message()) + db.close() + if modifyInfo.command in ['modify Phone_Number', 'modify Password', 'delete account']: + session.clear() + session.pop("user_id", None) + return redirect(url_for('login')) + elif modifyInfo.command == 'modify Username': + return redirect(url_for('modify')) except Exception as e: db.rollback() print(e) flash(modifyInfo.get_fail_message()) - db.close() - return redirect(url_for('index')) \ No newline at end of file + db.close() + return redirect(url_for('modify')) diff --git a/Project/func/signup.py b/Project/func/signup.py index 38b217a..5cd61bb 100644 --- a/Project/func/signup.py +++ b/Project/func/signup.py @@ -1,15 +1,38 @@ -from flask import render_template, request, flash, redirect, url_for +from flask import render_template, request, redirect, url_for from .get_db import get_db +import re def signup(): + error_messages = { + 'username': '', + 'mobileNo': '', + 'password': '', + 'confirmPassword': '' + } + if request.method == 'GET': - return render_template('signup.html') + return render_template('signup.html', errors=error_messages) if request.method == 'POST': - id = request.form['cardCode'] - name = request.form['name'] + username = request.form['username'] phone_number = request.form['mobileNo'] password = request.form['encryptedPassword'] + confirm_password = request.form['encryptedConfirmPassword'] + + # Basic validation for phone number + if not re.match(r'^\d{11}$', phone_number): + error_messages['mobileNo'] = '手机号格式有误' + + # Check password length after MD5 hash + if len(password) != 32: # MD5 hash length is 32 characters + error_messages['password'] = '密码格式有误' + + # Confirm password validation + if password != confirm_password: + error_messages['confirmPassword'] = '两次输入的密码不一致' + + if any(error_messages.values()): + return render_template('signup.html', errors=error_messages) db = get_db() cursor = db.cursor() @@ -17,32 +40,34 @@ def signup(): # 检查已有用户 sql = """ SELECT COUNT(*) FROM Users \ - WHERE ID = %s; + WHERE Phone_number = %s; """ try: - cursor.execute(sql, (id,)) - id_exist = cursor.fetchall()[0][0] + cursor.execute(sql, (phone_number,)) + phone_exist = cursor.fetchall()[0][0] except Exception as e: - flash("数据库异常,查询失败") + error_messages['mobileNo'] = "数据库异常,查询失败" print(e) - return redirect(url_for('signup')) - if (id_exist != 0): - flash("您已注册过,请勿重复注册") + return render_template('signup.html', errors=error_messages) + + if phone_exist != 0: + error_messages['mobileNo'] = "该手机号已注册,请勿重复注册" db.close() - return redirect(url_for('index')) + return render_template('signup.html', errors=error_messages) # 插入 sql = ''' - INSERT INTO passengers (ID, `Name`, Phone_number, `Password`) \ - VALUES (%s, %s, %s, %s); \ + INSERT INTO Users (Phone_number, Username, `Password`) \ + VALUES (%s, %s, %s); \ ''' try: - cursor.execute(sql, (id, name, phone_number, password)) + cursor.execute(sql, (phone_number, username, password)) db.commit() - flash("注册成功") + return redirect(url_for('index')) except Exception as e: db.rollback() print(e) - flash("数据库异常,注册失败") - db.close() - return redirect(url_for('index')) \ No newline at end of file + error_messages['mobileNo'] = "数据库异常,注册失败" + return render_template('signup.html', errors=error_messages) + finally: + db.close() diff --git a/Project/func/verify_user.py b/Project/func/verify_user.py deleted file mode 100644 index 8201e9b..0000000 --- a/Project/func/verify_user.py +++ /dev/null @@ -1,35 +0,0 @@ -from flask import flash, redirect, url_for -from pymysql.cursors import Cursor - -def verify_user(cursor:Cursor, id:str, password:str) -> str: - # 检查已有用户 - sql = """ - SELECT COUNT(*) FROM passengers \ - WHERE ID = %s; - """ - try: - cursor.execute(sql, (id,)) - id_exist = cursor.fetchall()[0][0] - except Exception as e: - flash("数据库异常,查询失败") - print(e) - return redirect(url_for('signup')) - if (id_exist == 0): - return "NO_USER" - - # 检查密码 - sql = """ - SELECT `Password` FROM passengers \ - WHERE ID = %s; - """ - try: - cursor.execute(sql, (id,)) - record_password = cursor.fetchall()[0][0] - except Exception as e: - flash("数据库异常,查询失败") - print(e) - return redirect(url_for('modify')) - if (record_password != password): - return "WRONG_PASSWORD" - - return "USER_VERIFIED" \ No newline at end of file diff --git a/Project/main.py b/Project/main.py index 623f790..9da23de 100644 --- a/Project/main.py +++ b/Project/main.py @@ -1,22 +1,80 @@ -from flask import Flask -import os +from flask import Flask, redirect, url_for, session, render_template, request, g +from flask_httpauth import HTTPTokenAuth +from flask_cors import CORS +from func.config import db, SECRET_KEY, slideshow_images +import pymysql -import func.index +import func.login import func.signup import func.modify +import func.index -app = Flask(__name__) -app.secret_key = os.environ.get('SECRET_KEY', 'OPTIONALSECRETKEY') +app = Flask(__name__) +app.config["SECRET_KEY"] = SECRET_KEY +app.config["JSON_AS_ASCII"] = False +app.config['SESSION_COOKIE_HTTPONLY'] = True +app.config['SESSION_COOKIE_SECURE'] = False +app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' +CORS(app, supports_credentials=True) +auth = HTTPTokenAuth(scheme='Bearer') +@app.before_request +def before_request(): + g.user = None + g.name = None + if 'user_id' in session: + conn = pymysql.connect(**db) + cursor = conn.cursor(pymysql.cursors.DictCursor) + args = (session.get('user_id'),) + verify_sql = "SELECT Phone_number, Username FROM Users WHERE Phone_number = %s;" + cursor.execute(verify_sql, args) + res = cursor.fetchone() + if not res: + g.user = None + g.name = None + else: + g.user = res['Phone_number'] + g.name = res['Username'] @app.route("/") +def home(): + return redirect(url_for('index')) + +@app.route("/index", methods=['GET', 'POST']) def index(): return func.index.index() -@app.route("/signup.html", methods=('GET', 'POST')) +@app.route("/login", methods=['GET', 'POST']) +def login(): + return func.login.login() + +@app.route('/logout') +def logout(): + return func.index.logout() + +@app.route("/signup", methods=['GET', 'POST']) def signup(): return func.signup.signup() -@app.route("/modify.html", methods=('GET', 'POST')) +@app.route("/modify", methods=['GET', 'POST']) def modify(): - return func.modify.modify() \ No newline at end of file + if request.method == 'GET': + if not g.user: + return redirect(url_for("login")) + return func.modify.modify() + +@app.route("/search", methods=['GET']) +def search(): + # 处理搜索逻辑 + return "搜索结果" + +@app.route("/orders", methods=['GET', 'POST']) +def orders(): + return "我的订单" + +if __name__ == "__main__": + app.run( + host="0.0.0.0", + port=5000, + debug=True + ) diff --git a/Project/static/css/index.css b/Project/static/css/index.css index f9854f9..3943054 100644 --- a/Project/static/css/index.css +++ b/Project/static/css/index.css @@ -7,53 +7,124 @@ body { display: flex; flex-direction: column; align-items: center; + overflow-x: hidden; /* 防止出现水平滚动条 */ } header { + display: flex; + justify-content: space-between; + align-items: center; background-color: rgba(28, 108, 178, 0.9); color: white; - padding: 20px 0; - text-align: center; + padding: 20px; width: 100%; } -nav ul { - list-style: none; - padding: 0; - margin: 0; +.header-content { + display: flex; + align-items: center; + width: 100%; + justify-content: space-between; } -nav ul li { - display: inline; - margin: 0 10px; +.logo { + font-size: 24px; + font-weight: bold; + margin-left: 20px; } -nav ul li a { +.nav-buttons { + display: flex; + gap: 20px; +} + +.nav-buttons a { color: white; text-decoration: none; - font-weight: bold; + font-size: 16px; + padding: 10px; + border-radius: 5px; } -main { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - width: 100%; +.nav-buttons a:hover { + background-color: #155a8c; } -.background-container { - width: 100%; - max-width: 1500px; - height: 600px; /* Adjust according to the desired size */ - background-image: url('/static/images/banner-login-20200629.jpg'); - background-size: cover; - background-position: center; - background-repeat: no-repeat; - display: flex; - align-items: center; - justify-content: center; +.user-menu { position: relative; + display: flex; + align-items: center; + margin-right: 20px; +} + +.user-menu span { + margin-right: 10px; +} + +.dropdown { + position: relative; + display: inline-block; +} + +.dropbtn { + background-color: rgba(28, 108, 178, 0.9); + color: white; + border: none; + cursor: pointer; + font-size: 16px; +} + +.dropdown-content { + display: none; + position: absolute; + background-color: #f9f9f9; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; + right: 0; /* 确保下拉菜单靠右对齐 */ +} + +.dropdown-content a { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; +} + +.dropdown-content a:hover { + background-color: #f1f1f1; +} + +.dropdown:hover .dropdown-content { + display: block; +} + +.slides { + position: relative; + width: 100%; + height: 400px; + overflow: hidden; +} + +.slides ul { + display: flex; + padding: 0; + width: 100%; + height: 100%; + list-style: none; + margin: 0; + transition: transform 0.6s ease-in-out; +} + +.slides li { + min-width: 100%; + box-sizing: border-box; +} + +.slides img { + width: 100%; + height: 100%; + object-fit: cover; } .content { @@ -61,28 +132,79 @@ main { padding: 20px; border-radius: 10px; text-align: center; - position: absolute; + width: 80%; + max-width: 800px; + margin: -50px auto 20px; + position: relative; } -h2 { - color: #1c6cb2; +.tabcontent { + display: block; + padding: 20px; } -.links { - margin-top: 20px; +.form-row { + display: flex; + justify-content: space-between; + margin-bottom: 15px; } -.links .btn { +.form-row label { + flex: 1; + margin-right: 10px; + text-align: right; + line-height: 32px; +} + +.form-row input, +.form-row select { + flex: 2; + padding: 8px; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 5px; +} + +.passenger-input { + display: flex; + align-items: center; + gap: 5px; +} + +.passenger-input button { + padding: 5px 10px; + font-size: 18px; background-color: #1c6cb2; color: white; - padding: 10px 20px; - text-decoration: none; + border: none; border-radius: 5px; - margin: 10px; - display: inline-block; + cursor: pointer; } -.links .btn:hover { +.passenger-input button:hover { + background-color: #155a8c; +} + +.passenger-input input { + width: 50px; + text-align: center; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 5px; +} + +.btn { + padding: 10px 20px; + background-color: #1c6cb2; + color: white; + border: none; + border-radius: 5px; + text-decoration: none; + display: inline-block; + cursor: pointer; +} + +.btn:hover { background-color: #155a8c; } @@ -92,4 +214,12 @@ footer { text-align: center; padding: 10px 0; width: 100%; + margin-top: auto; +} + +.error-message { + color: red; + font-size: 12px; + text-align: left; + margin-top: 5px; } diff --git a/Project/static/css/login.css b/Project/static/css/login.css new file mode 100644 index 0000000..ac9ce9b --- /dev/null +++ b/Project/static/css/login.css @@ -0,0 +1,121 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #ffffff; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; +} + +header { + background-color: rgba(28, 108, 178, 0.9); + color: white; + padding: 20px 0; + text-align: center; + width: 100%; +} + +main { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + position: relative; +} + +.slides { + position: relative; + width: 100%; + height: 600px; + overflow: hidden; +} + +.slides ul { + display: flex; + padding: 0; + width: 100%; + height: 100%; + list-style: none; + margin: 0; + transition: transform 0.6s ease-in-out; +} + +.slides li { + min-width: 100%; + box-sizing: border-box; +} + +.slides img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.content { + background-color: rgba(255, 255, 255, 0.8); + padding: 20px; + border-radius: 10px; + text-align: center; + position: absolute; +} + +h2 { + color: #1c6cb2; +} + +.login-form { + margin-bottom: 20px; +} + +.login-form input { + display: block; + margin: 10px auto; + padding: 10px; + font-size: 16px; + width: 80%; + max-width: 300px; + margin-bottom: 20px; /* 增加外边距 */ +} + +.buttons { + display: flex; + justify-content: center; + gap: 10px; +} + +.login-form button, +.btn { + padding: 10px 20px; + background-color: #1c6cb2; + color: white; + border: none; + border-radius: 5px; + text-decoration: none; + display: inline-block; + line-height: 20px; + cursor: pointer; +} + +.login-form button:hover, +.btn:hover { + background-color: #155a8c; +} + +footer { + background-color: rgba(28, 108, 178, 0.9); + color: white; + text-align: center; + padding: 10px 0; + width: 100%; +} + +.error-message { + color: red; + font-size: 12px; + text-align: left; + margin-top: -10px; + margin-bottom: 10px; +} diff --git a/Project/static/css/modify.css b/Project/static/css/modify.css new file mode 100644 index 0000000..8f0fb54 --- /dev/null +++ b/Project/static/css/modify.css @@ -0,0 +1,144 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #ffffff; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; +} + +header { + background-color: rgba(28, 108, 178, 0.9); + color: white; + padding: 20px 0; + width: 100%; +} + +.header-content { + display: flex; + align-items: center; + justify-content: space-between; + width: 90%; + margin: 0 auto; + position: relative; +} + +.header-content .btn-back { + padding: 10px 20px; + background-color: #1c6cb2; + color: white; + border: none; + border-radius: 5px; + text-decoration: none; +} + +.header-content .btn-back:hover { + background-color: #155a8c; +} + +.header-content h1 { + position: absolute; + left: 50%; + transform: translateX(-50%); +} + +.header-content .header-spacer { + width: 100px; /* 占位符,确保标题居中 */ +} + +main { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + position: relative; +} + +.content { + background-color: rgba(255, 255, 255, 0.9); + padding: 30px; + border-radius: 10px; + text-align: center; + width: 400px; + position: relative; +} + +h2 { + color: #1c6cb2; +} + +form { + margin-top: 20px; +} + +.tab { + display: flex; + justify-content: space-around; + margin-bottom: 20px; +} + +.tab button { + background-color: #f1f1f1; + border: none; + outline: none; + cursor: pointer; + padding: 10px 20px; + transition: 0.3s; + font-size: 17px; + border-radius: 5px; +} + +.tab button:hover { + background-color: #ddd; +} + +.tab button.active { + background-color: #1c6cb2; + color: white; +} + +.tabcontent { + display: none; +} + +.form-group { + margin-bottom: 15px; + text-align: left; +} + +.form-group div { + margin-bottom: 5px; + font-weight: bold; +} + +.form-group input { + width: 100%; + padding: 10px; + box-sizing: border-box; +} + +button[type="submit"] { + padding: 10px 20px; + background-color: #1c6cb2; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + width: 100%; + font-size: 16px; +} + +button[type="submit"]:hover { + background-color: #155a8c; +} + +footer { + background-color: rgba(28, 108, 178, 0.9); + color: white; + text-align: center; + padding: 10px 0; + width: 100%; +} diff --git a/Project/static/css/signup.css b/Project/static/css/signup.css new file mode 100644 index 0000000..7946c39 --- /dev/null +++ b/Project/static/css/signup.css @@ -0,0 +1,71 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #ffffff; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; +} + +form { + background-color: rgba(255, 255, 255, 0.9); + padding: 40px; /* 增加内边距 */ + border-radius: 10px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 500px; /* 增大表单框的最大宽度 */ + margin: auto; + margin-top: 100px; +} + +ul { + list-style-type: none; + padding: 0; + margin: 0; +} + +li { + margin-bottom: 20px; +} + +div { + display: flex; + flex-direction: column; +} + +div div { + flex: 1; + padding-top: 5px; +} + +input[type="text"], input[type="password"] { + width: 100%; + padding: 10px; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 5px; +} + +button { + width: 100%; + padding: 10px; + background-color: #1c6cb2; + color: white; + border: none; + border-radius: 5px; + font-size: 16px; + cursor: pointer; +} + +button:hover { + background-color: #155a8c; +} + +.error-message { + color: red; + font-size: 12px; + height: 12px; + margin-top: 5px; +} diff --git a/Project/static/css/style.css b/Project/static/css/style.css deleted file mode 100644 index 34abe50..0000000 --- a/Project/static/css/style.css +++ /dev/null @@ -1,9 +0,0 @@ -/* h1, ul, li, a, body { - margin: 0; - padding: 0; - text-decoration: none; -} */ - -li { - list-style: none; -} \ No newline at end of file diff --git a/Project/static/images/banner-login-20200629.jpg b/Project/static/images/banner-login-20200629.jpg deleted file mode 100644 index 21677f4..0000000 Binary files a/Project/static/images/banner-login-20200629.jpg and /dev/null differ diff --git a/Project/static/js/checkInfo.js b/Project/static/js/checkInfo.js index 641c012..d81ebe4 100644 --- a/Project/static/js/checkInfo.js +++ b/Project/static/js/checkInfo.js @@ -1,51 +1,15 @@ -var checkInfo = {} - -checkInfo.checkCardCode = function() { - let cardCode = document.getElementById('cardCode').value - let regexCardCode = /^([1-6][1-9]|50)\d{4}(18|19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/ - if(!regexCardCode.test(cardCode)) { - alert('身份证号格式有误') - return false - } - return true -} +var checkInfo = {}; checkInfo.checkMobileNo = function() { - let mobileNo = document.getElementById('mobileNo').value - let regexMobileNo = /^1[3-9]\d{9}$/ - if (!regexMobileNo.test(mobileNo)) { - alert('手机号格式有误') - return false - } - return true + let mobileNo = document.getElementById('mobileNo').value; + let regexMobileNo = /^1[3-9]\d{9}$/; + return regexMobileNo.test(mobileNo); } checkInfo.checkPassword = function() { - let password = document.getElementById('password') - let regexPassword = /^[A-Za-z0-9\W_]{6,20}$/ - if (!regexPassword.test(password.value)) { - alert("密码须为长度为6-20位字母、数字或符号") - return false - } - let confirmPassword = document.getElementById('confirmPassword') - if (password.value !== confirmPassword.value) { - alert("两次输入的密码不一致") - return false - } - return true + let password = document.getElementById('password').value; + let regexPassword = /^[A-Za-z0-9\W_]{6,20}$/; + let isValidPassword = regexPassword.test(password); + let confirmPassword = document.getElementById('confirmPassword').value; + return isValidPassword && (password === confirmPassword); } - -checkInfo.checkNewPassword = function() { - let password = document.getElementById('newPassword') - let regexPassword = /^[A-Za-z0-9\W_]{6,20}$/ - if (!regexPassword.test(password.value)) { - alert("密码须为长度为6-20位字母、数字或符号") - return false - } - let confirmPassword = document.getElementById('confirmNewPassword') - if (password.value !== confirmPassword.value) { - alert("两次输入的密码不一致") - return false - } - return true -} \ No newline at end of file diff --git a/Project/static/js/index.js b/Project/static/js/index.js index 7190b81..6e16fa8 100644 --- a/Project/static/js/index.js +++ b/Project/static/js/index.js @@ -1,8 +1,29 @@ -window.onload = function() { - // Display flashed messages, if any - const messages = []; - // Assuming messages are added via server-side templating - if (messages.length > 0) { - alert(messages.join("\n")); +function openTab(evt, tabName) { + var i, tabcontent, tablinks; + tabcontent = document.getElementsByClassName("tabcontent"); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; } -}; + tablinks = document.getElementsByClassName("tablinks"); + for (i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + document.getElementById(tabName).style.display = "block"; + evt.currentTarget.className += " active"; +} + +function increment() { + var passengers = document.getElementById("passengers"); + var value = parseInt(passengers.value, 10); + if (value < 50) { + passengers.value = value + 1; + } +} + +function decrement() { + var passengers = document.getElementById("passengers"); + var value = parseInt(passengers.value, 10); + if (value > 1) { + passengers.value = value - 1; + } +} diff --git a/Project/static/js/login.js b/Project/static/js/login.js new file mode 100644 index 0000000..bed1617 --- /dev/null +++ b/Project/static/js/login.js @@ -0,0 +1,81 @@ +window.onload = function() { + autoLogin(); +}; + +var checkInfo = {}; + +checkInfo.checkMobileNo = function() { + let mobileNo = document.getElementById('mobileNo').value; + let regexMobileNo = /^1[3-9]\d{9}$/; + if (!regexMobileNo.test(mobileNo)) { + document.getElementById('mobileNoError').textContent = '手机号格式有误'; + return false; + } + document.getElementById('mobileNoError').textContent = ''; + return true; +} + +checkInfo.checkPassword = function() { + let password = document.getElementById('password').value; + let regexPassword = /^[A-Za-z0-9\W_]{6,20}$/; + if (!regexPassword.test(password)) { + document.getElementById('loginError').textContent = "密码须为长度为6-20位字母、数字或符号"; + return false; + } + document.getElementById('loginError').textContent = ''; + return true; +} + +function submitForm() { + if (checkInfo.checkMobileNo() && checkInfo.checkPassword()) { + document.getElementById('encryptedPassword').value = md5( + document.getElementById('password').value + ); + login(); + return true; + } + return false; +} + +// 登录函数 +async function login() { + const mobileNo = document.getElementById('mobileNo').value; + const encryptedPassword = document.getElementById('encryptedPassword').value; + try { + const response = await fetch('/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ username: mobileNo, password: encryptedPassword }), + credentials: 'include' // 确保请求包含凭据(cookies) + }); + const data = await response.json(); + if (response.ok) { + alert('登录成功'); + // 自动跳转到主页 + window.location.href = data.redirect; + } else { + document.getElementById('loginError').textContent = data.message; + } + } catch (error) { + alert('数据库错误,请稍后再试'); + } +} + +// 自动登录函数 +async function autoLogin() { + const token = localStorage.getItem('token'); + if (token) { + const response = await fetch('http://localhost:5000/index', { + headers: { + 'Authorization': 'Bearer ' + token + } + }); + if (response.ok) { + document.getElementById('content').innerText = '已自动登录'; + } else { + document.getElementById('content').innerText = '自动登录失败'; + } + } +} diff --git a/Project/static/js/modify.js b/Project/static/js/modify.js index 084e9f4..5dd6957 100644 --- a/Project/static/js/modify.js +++ b/Project/static/js/modify.js @@ -1,28 +1,31 @@ -var modify = {} +var modify = {}; modify.showModifyPassword = function() { - let modifyType = document.getElementById('modifyType').value + let modifyType = document.querySelector('.tablinks.active').textContent; let info = { modifyPasswordLis: document.getElementsByClassName('modifyPassword'), modifymobileNoLis: document.getElementsByClassName('modifymobileNo'), + modifyUsernameLis: document.getElementsByClassName('modifyUsername'), } - // 遍历隐藏所有元素 for (let key in info) { let elements = info[key]; for (let item of elements) { - item.style.display = 'none'; // 确保所有相关元素被隐藏 + item.style.display = 'none'; } } - // 根据 modifyType 显示相关元素 - if (modifyType === "2") { + if (modifyType === "修改密码") { for (let item of info.modifyPasswordLis) { item.style.display = 'block'; } - } else if (modifyType === "3") { + } else if (modifyType === "修改手机号") { for (let item of info.modifymobileNoLis) { item.style.display = 'block'; } + } else if (modifyType === "修改用户名") { + for (let item of info.modifyUsernameLis) { + item.style.display = 'block'; + } } -} \ No newline at end of file +} diff --git a/Project/static/js/signup.js b/Project/static/js/signup.js new file mode 100644 index 0000000..bf0ba4f --- /dev/null +++ b/Project/static/js/signup.js @@ -0,0 +1,32 @@ +function submitForm() { + let isValid = true; + clearErrors(); + + if (!checkInfo.checkMobileNo()) { + document.getElementById('mobileNoError').innerText = '手机号格式有误'; + isValid = false; + } + + if (!checkInfo.checkPassword()) { + document.getElementById('passwordError').innerText = '密码须为长度为6-20位字母、数字或符号'; + document.getElementById('confirmPasswordError').innerText = '两次输入的密码不一致'; + isValid = false; + } + + if (isValid) { + document.getElementById('encryptedPassword').value = md5( + document.getElementById('password').value + ); + document.getElementById('encryptedConfirmPassword').value = md5( + document.getElementById('confirmPassword').value + ); + return true; + } + return false; +} + +function clearErrors() { + document.getElementById('mobileNoError').innerText = ''; + document.getElementById('passwordError').innerText = ''; + document.getElementById('confirmPasswordError').innerText = ''; +} diff --git a/Project/static/js/slideshow.js b/Project/static/js/slideshow.js new file mode 100644 index 0000000..c7af2c5 --- /dev/null +++ b/Project/static/js/slideshow.js @@ -0,0 +1,14 @@ +let slideIndex = 0; +const slides = document.getElementById('slide-container'); +const totalSlides = slides.children.length; + +function showSlides() { + slideIndex++; + if (slideIndex >= totalSlides) { + slideIndex = 0; + } + slides.style.transform = 'translateX(' + (-slideIndex * 100) + '%)'; + setTimeout(showSlides, 5000); // Change image every 5 seconds +} + +showSlides(); diff --git a/Project/templates/index.html b/Project/templates/index.html index a9783d0..2b5f51e 100644 --- a/Project/templates/index.html +++ b/Project/templates/index.html @@ -3,34 +3,129 @@ - 我的12306官网 + KJF航班订票 +
-

中国铁路12306

- +
+ + +
+ {{ username }} + +
+
-
-
-

尽享精彩出行服务

- +
+
    + {% for image in images %} +
  • + + + +
  • + {% endfor %} +
+
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+ + + +
+
+
+ +
+
+ + diff --git a/Project/templates/login.html b/Project/templates/login.html new file mode 100644 index 0000000..d6cb6cb --- /dev/null +++ b/Project/templates/login.html @@ -0,0 +1,47 @@ + + + + + + 我的航班订票官网 + + + + + +
+

KJF航班订票

+
+
+
+ +
+
+

尽享精彩出行服务

+ +
+
+ + + + diff --git a/Project/templates/modify.html b/Project/templates/modify.html index dcd57dc..31eae07 100644 --- a/Project/templates/modify.html +++ b/Project/templates/modify.html @@ -1,10 +1,12 @@ - 修改账户 - - - + - - + + + - \ No newline at end of file + diff --git a/Project/templates/signup.html b/Project/templates/signup.html index 518e500..9e920c2 100644 --- a/Project/templates/signup.html +++ b/Project/templates/signup.html @@ -1,115 +1,51 @@ - 我的12306注册 - - - + + + 注册 - KJF航班订票 + + + + - -
- - -
- - - +
+ +
- \ No newline at end of file +