完成大作业服务端代码,新建管理端,管理端不可用
This commit is contained in:
BIN
Project/Service/__pycache__/config.cpython-311.pyc
Normal file
BIN
Project/Service/__pycache__/config.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/__pycache__/main.cpython-311.pyc
Normal file
BIN
Project/Service/__pycache__/main.cpython-311.pyc
Normal file
Binary file not shown.
0
Project/Service/func/__init__.py
Normal file
0
Project/Service/func/__init__.py
Normal file
BIN
Project/Service/func/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/book.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/book.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/cancel_order.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/cancel_order.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/config.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/config.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/get_db.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/get_db.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/index.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/index.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/login.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/login.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/modify.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/modify.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/order.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/order.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/order_list.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/order_list.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/pay.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/pay.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/pay_confirm.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/pay_confirm.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/search.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/search.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/signup.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/signup.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/utils.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/utils.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Project/Service/func/__pycache__/verify_user.cpython-311.pyc
Normal file
BIN
Project/Service/func/__pycache__/verify_user.cpython-311.pyc
Normal file
Binary file not shown.
92
Project/Service/func/book.py
Normal file
92
Project/Service/func/book.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from flask import render_template, request, redirect, url_for, g, flash, session
|
||||
from .config import db
|
||||
import pymysql
|
||||
from datetime import datetime
|
||||
|
||||
def book():
|
||||
flight_id = request.args.get('flight_id')
|
||||
if request.method == 'GET':
|
||||
if not g.user:
|
||||
return redirect(url_for("login"))
|
||||
conn = pymysql.connect(**db)
|
||||
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||
flight_sql = """
|
||||
SELECT f.*, d.Name as Departure_airport_name, a.Name as Arrival_airport_name
|
||||
FROM Flights f
|
||||
JOIN Airports d ON f.Departure_airport = d.ID
|
||||
JOIN Airports a ON f.Arrival_airport = a.ID
|
||||
WHERE f.ID = %s;
|
||||
"""
|
||||
cursor.execute(flight_sql, (flight_id,))
|
||||
flight = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
return render_template('book.html', flight=flight, username=g.name)
|
||||
|
||||
if request.method == 'POST':
|
||||
passengers = []
|
||||
passenger_keys = ['card_code', 'name', 'phone_number', 'seat_class']
|
||||
|
||||
for i in range(len(request.form) // len(passenger_keys)):
|
||||
passenger = {}
|
||||
for key in passenger_keys:
|
||||
passenger[key] = request.form.get(f'passengers[{i}][{key}]')
|
||||
passengers.append(passenger)
|
||||
|
||||
conn = pymysql.connect(**db)
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
# 插入订单记录
|
||||
insert_order_sql = """
|
||||
INSERT INTO Orders (Order_time, Paid, User_phone_number)
|
||||
VALUES (%s, 0, %s)
|
||||
"""
|
||||
cursor.execute(insert_order_sql, (datetime.now(), g.user))
|
||||
order_id = cursor.lastrowid
|
||||
|
||||
for passenger in passengers:
|
||||
passenger_id = passenger['card_code']
|
||||
name = passenger['name']
|
||||
phone_number = passenger['phone_number']
|
||||
seat_class = passenger['seat_class']
|
||||
|
||||
price_field = f"{seat_class.replace(' ', '_').lower()}_price"
|
||||
price_sql = f"SELECT {price_field} FROM Flights WHERE ID = %s"
|
||||
cursor.execute(price_sql, (flight_id,))
|
||||
price = cursor.fetchone()[0]
|
||||
|
||||
insert_passenger_sql = """
|
||||
INSERT INTO Passengers (ID, Name, Phone_number)
|
||||
VALUES (%s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE Name=VALUES(Name), Phone_number=VALUES(Phone_number);
|
||||
"""
|
||||
cursor.execute(insert_passenger_sql, (passenger_id, name, phone_number))
|
||||
|
||||
update_seat_sql = f"""
|
||||
UPDATE Flights
|
||||
SET {seat_class.replace(' ', '_').lower()}_seats_remaining = {seat_class.replace(' ', '_').lower()}_seats_remaining - 1
|
||||
WHERE ID = %s
|
||||
"""
|
||||
cursor.execute(update_seat_sql, (flight_id,))
|
||||
|
||||
insert_ticket_sql = """
|
||||
INSERT INTO Tickets (Price, FlightID, Seat_class, PassengerID, OrderID)
|
||||
VALUES (%s, %s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(insert_ticket_sql, (price, flight_id, seat_class, passenger_id, order_id))
|
||||
|
||||
conn.commit()
|
||||
return redirect(url_for('order', order_id=order_id))
|
||||
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
print(e)
|
||||
flash("订票失败", "error")
|
||||
return redirect(url_for('search'))
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
conn.close()
|
||||
51
Project/Service/func/cancel_order.py
Normal file
51
Project/Service/func/cancel_order.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from flask import request, redirect, url_for, g
|
||||
from .config import db
|
||||
import pymysql
|
||||
|
||||
def cancel_order():
|
||||
order_id = request.args.get('order_id')
|
||||
if not g.user:
|
||||
return redirect(url_for("login"))
|
||||
conn = pymysql.connect(**db)
|
||||
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||
|
||||
# 检查订单是否存在
|
||||
check_order_sql = "SELECT ID FROM Orders WHERE ID = %s"
|
||||
cursor.execute(check_order_sql, (order_id,))
|
||||
order_exists = cursor.fetchone()
|
||||
|
||||
if not order_exists:
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return redirect(url_for("order_list"))
|
||||
|
||||
# 查询订单中所有机票的航班ID和座位级别
|
||||
tickets_sql = "SELECT FlightID, Seat_class FROM Tickets WHERE OrderID = %s"
|
||||
cursor.execute(tickets_sql, (order_id,))
|
||||
tickets = cursor.fetchall()
|
||||
|
||||
# 恢复航班的对应余座数
|
||||
for ticket in tickets:
|
||||
flight_id = ticket['FlightID']
|
||||
seat_class = ticket['Seat_class']
|
||||
seat_column = seat_class.replace(' ', '_').lower() + "_seats_remaining"
|
||||
update_seat_sql = f"""
|
||||
UPDATE Flights
|
||||
SET {seat_column} = {seat_column} + 1
|
||||
WHERE ID = %s
|
||||
"""
|
||||
cursor.execute(update_seat_sql, (flight_id,))
|
||||
|
||||
# 删除对应的机票
|
||||
delete_tickets_sql = "DELETE FROM Tickets WHERE OrderID = %s"
|
||||
cursor.execute(delete_tickets_sql, (order_id,))
|
||||
|
||||
# 删除订单
|
||||
delete_order_sql = "DELETE FROM Orders WHERE ID = %s"
|
||||
cursor.execute(delete_order_sql, (order_id,))
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
return redirect(url_for('order_list'))
|
||||
18
Project/Service/func/config.py
Normal file
18
Project/Service/func/config.py
Normal file
@@ -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"}
|
||||
]
|
||||
18
Project/Service/func/index.py
Normal file
18
Project/Service/func/index.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from flask import render_template, request, g, redirect, url_for, session
|
||||
from .config import slideshow_images
|
||||
from .utils import get_cities
|
||||
|
||||
def index():
|
||||
if request.method == 'GET':
|
||||
if not g.user:
|
||||
return redirect(url_for("login"))
|
||||
images = slideshow_images
|
||||
return render_template(
|
||||
'index.html', cities=get_cities(),
|
||||
images=images, username=g.name
|
||||
)
|
||||
|
||||
def logout():
|
||||
session.clear()
|
||||
session.pop('user_id', None)
|
||||
return redirect(url_for('login'))
|
||||
35
Project/Service/func/login.py
Normal file
35
Project/Service/func/login.py
Normal file
@@ -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
|
||||
119
Project/Service/func/modify.py
Normal file
119
Project/Service/func/modify.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from flask import render_template, request, flash, redirect, url_for, session, g
|
||||
from typing import Dict
|
||||
from pymysql.cursors import Cursor
|
||||
import pymysql
|
||||
from .config import db
|
||||
|
||||
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], user_phone: str):
|
||||
self.phone_number = user_phone
|
||||
modifyType = form['modifyType']
|
||||
self.new_password = form.get('encryptedNewPassword', None)
|
||||
self.new_phone_number = form.get('mobileNo', None)
|
||||
self.new_username = form.get('username', None)
|
||||
modifyType2command = {
|
||||
'删除账户': 'delete account',
|
||||
'修改密码': 'modify Password',
|
||||
'修改手机号': 'modify Phone_Number',
|
||||
'修改用户名': 'modify Username'
|
||||
}
|
||||
self.sql_dict = {
|
||||
'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.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 Username': "修改用户名成功"
|
||||
}
|
||||
self.fail_message_dict = {
|
||||
'delete account': "数据库异常,删除账户失败",
|
||||
'modify Password': "数据库异常,修改密码失败",
|
||||
'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':
|
||||
if not g.user:
|
||||
return redirect(url_for("login"))
|
||||
user_phone = session.get('user_id')
|
||||
return render_template('modify.html', current_user_phone=user_phone, current_username=g.name)
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
user_phone = session.get('user_id')
|
||||
password = request.form['encryptedPassword']
|
||||
conn = pymysql.connect(**db)
|
||||
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||
|
||||
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("密码错误")
|
||||
conn.close()
|
||||
return redirect(url_for('modify'))
|
||||
|
||||
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("手机号已存在,请使用其他手机号")
|
||||
conn.close()
|
||||
return redirect(url_for('modify'))
|
||||
|
||||
try:
|
||||
cursor.execute(modifyInfo.get_sql(), modifyInfo.get_args())
|
||||
conn.commit()
|
||||
flash(modifyInfo.get_ok_message())
|
||||
conn.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:
|
||||
conn.rollback()
|
||||
print(e)
|
||||
flash(modifyInfo.get_fail_message())
|
||||
conn.close()
|
||||
return redirect(url_for('modify'))
|
||||
47
Project/Service/func/order.py
Normal file
47
Project/Service/func/order.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from flask import render_template, request, redirect, url_for, g
|
||||
from .config import db
|
||||
import pymysql
|
||||
|
||||
def order():
|
||||
order_id = request.args.get('order_id')
|
||||
if not g.user:
|
||||
return redirect(url_for("login"))
|
||||
|
||||
conn = pymysql.connect(**db)
|
||||
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||
|
||||
# 查询订单信息和航班信息
|
||||
order_sql = """
|
||||
SELECT o.*, f.*, d.Name as Departure_airport_name, a.Name as Arrival_airport_name
|
||||
FROM Orders o
|
||||
JOIN Tickets t ON o.ID = t.OrderID
|
||||
JOIN Flights f ON t.FlightID = f.ID
|
||||
JOIN Airports d ON f.Departure_airport = d.ID
|
||||
JOIN Airports a ON f.Arrival_airport = a.ID
|
||||
WHERE o.ID = %s
|
||||
"""
|
||||
cursor.execute(order_sql, (order_id,))
|
||||
order_info = cursor.fetchone()
|
||||
|
||||
# 如果订单信息不存在,返回订单列表页面
|
||||
if not order_info:
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return redirect(url_for("order_list"))
|
||||
|
||||
# 查询乘客信息和票据信息
|
||||
tickets_sql = """
|
||||
SELECT t.*, p.Name, p.Phone_number
|
||||
FROM Tickets t
|
||||
JOIN Passengers p ON t.PassengerID = p.ID
|
||||
WHERE t.OrderID = %s
|
||||
"""
|
||||
cursor.execute(tickets_sql, (order_id,))
|
||||
tickets = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
total_price = sum(ticket['Price'] for ticket in tickets)
|
||||
|
||||
return render_template('order.html', order=order_info, tickets=tickets, total_price=total_price, username=g.name)
|
||||
54
Project/Service/func/order_list.py
Normal file
54
Project/Service/func/order_list.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from flask import render_template, request, redirect, url_for, g
|
||||
from .config import db
|
||||
import pymysql
|
||||
|
||||
def order_list():
|
||||
if not g.user:
|
||||
return redirect(url_for("login"))
|
||||
|
||||
conn = pymysql.connect(**db)
|
||||
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||
|
||||
# 查询用户关联的所有订单信息
|
||||
orders_sql = """
|
||||
SELECT o.ID as OrderID, o.Order_time, o.Paid, f.ID as FlightID, f.Airline,
|
||||
d.Name as Departure_airport_name, a.Name as Arrival_airport_name,
|
||||
f.Departure_time, f.Status, p.Name as PassengerName, t.Price
|
||||
FROM Orders o
|
||||
JOIN Tickets t ON o.ID = t.OrderID
|
||||
JOIN Flights f ON t.FlightID = f.ID
|
||||
JOIN Airports d ON f.Departure_airport = d.ID
|
||||
JOIN Airports a ON f.Arrival_airport = a.ID
|
||||
JOIN Passengers p ON t.PassengerID = p.ID
|
||||
WHERE o.User_phone_number = %s
|
||||
"""
|
||||
cursor.execute(orders_sql, (g.user,))
|
||||
orders = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
# 整理订单信息
|
||||
order_dict = {}
|
||||
for order in orders:
|
||||
order_id = order['OrderID']
|
||||
if order_id not in order_dict:
|
||||
order_dict[order_id] = {
|
||||
'OrderID': order_id,
|
||||
'Order_time': order['Order_time'],
|
||||
'Paid': order['Paid'],
|
||||
'FlightID': order['FlightID'],
|
||||
'Airline': order['Airline'],
|
||||
'Departure_airport_name': order['Departure_airport_name'],
|
||||
'Arrival_airport_name': order['Arrival_airport_name'],
|
||||
'Departure_time': order['Departure_time'],
|
||||
'Status': order['Status'],
|
||||
'Passengers': [],
|
||||
'TotalPrice': 0
|
||||
}
|
||||
order_dict[order_id]['Passengers'].append(order['PassengerName'])
|
||||
order_dict[order_id]['TotalPrice'] += order['Price']
|
||||
|
||||
order_list = list(order_dict.values())
|
||||
|
||||
return render_template('order_list.html', orders=order_list, username=g.name)
|
||||
30
Project/Service/func/pay_confirm.py
Normal file
30
Project/Service/func/pay_confirm.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from flask import redirect, url_for, g, request
|
||||
from .config import db
|
||||
import pymysql
|
||||
|
||||
def pay_confirm():
|
||||
order_id = request.args.get('order_id')
|
||||
if not g.user:
|
||||
return redirect(url_for("login"))
|
||||
|
||||
conn = pymysql.connect(**db)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 检查订单是否存在
|
||||
check_order_sql = "SELECT ID FROM Orders WHERE ID = %s"
|
||||
cursor.execute(check_order_sql, (order_id,))
|
||||
order_exists = cursor.fetchone()
|
||||
|
||||
if not order_exists:
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return redirect(url_for("order_list"))
|
||||
|
||||
update_order_sql = "UPDATE Orders SET Paid = 1 WHERE ID = %s"
|
||||
cursor.execute(update_order_sql, (order_id,))
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
return redirect(url_for('order_list', order_id=order_id))
|
||||
49
Project/Service/func/search.py
Normal file
49
Project/Service/func/search.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from flask import render_template, request, g, abort, redirect, url_for
|
||||
from .config import db
|
||||
from .utils import get_cities
|
||||
import pymysql
|
||||
import datetime
|
||||
|
||||
|
||||
def search():
|
||||
if not g.user:
|
||||
return redirect(url_for("login"))
|
||||
departure_city = request.args.get('departure')
|
||||
destination_city = request.args.get('destination')
|
||||
departure_date = request.args.get('departure-date')
|
||||
passengers = int(request.args.get('passengers', 1))
|
||||
|
||||
# Date validation
|
||||
try:
|
||||
departure_date_obj = datetime.datetime.strptime(departure_date, '%Y-%m-%d').date()
|
||||
if departure_date_obj < datetime.date.today():
|
||||
abort(400, description="Departure date cannot be in the past.")
|
||||
except ValueError:
|
||||
abort(400, description="Invalid date format.")
|
||||
|
||||
conn = pymysql.connect(**db)
|
||||
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||
|
||||
search_sql = """
|
||||
SELECT f.*, d.Name as Departure_airport_name, a.Name as Arrival_airport_name
|
||||
FROM Flights f
|
||||
JOIN Airports d ON f.Departure_airport = d.ID
|
||||
JOIN Airports a ON f.Arrival_airport = a.ID
|
||||
WHERE d.City = %s AND a.City = %s
|
||||
AND DATE(f.Departure_time) = %s
|
||||
AND (f.First_class_seats_remaining + f.Business_class_seats_remaining + f.Economy_class_seats_remaining) >= %s
|
||||
AND f.Status NOT IN ('已起飞', '已降落');
|
||||
"""
|
||||
|
||||
cursor.execute(search_sql, (departure_city, destination_city, departure_date, passengers))
|
||||
flights = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
return render_template(
|
||||
'search.html',
|
||||
cities=get_cities(),
|
||||
flights=flights,
|
||||
username=g.name
|
||||
)
|
||||
74
Project/Service/func/signup.py
Normal file
74
Project/Service/func/signup.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from flask import render_template, request, redirect, url_for
|
||||
from .config import db
|
||||
import re
|
||||
import pymysql
|
||||
|
||||
def signup():
|
||||
error_messages = {
|
||||
'username': '',
|
||||
'mobileNo': '',
|
||||
'password': '',
|
||||
'confirmPassword': ''
|
||||
}
|
||||
|
||||
if request.method == 'GET':
|
||||
return render_template('signup.html', errors=error_messages)
|
||||
|
||||
if request.method == 'POST':
|
||||
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)
|
||||
|
||||
conn = pymysql.connect(**db)
|
||||
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||
|
||||
# 检查已有用户
|
||||
sql = """
|
||||
SELECT COUNT(*) FROM Users \
|
||||
WHERE Phone_number = %s;
|
||||
"""
|
||||
try:
|
||||
cursor.execute(sql, (phone_number,))
|
||||
phone_exist = cursor.fetchall()[0]['COUNT(*)']
|
||||
except Exception as e:
|
||||
error_messages['mobileNo'] = "数据库异常,查询失败"
|
||||
print(e)
|
||||
return render_template('signup.html', errors=error_messages)
|
||||
|
||||
if phone_exist != 0:
|
||||
error_messages['mobileNo'] = "该手机号已注册,请勿重复注册"
|
||||
conn.close()
|
||||
return render_template('signup.html', errors=error_messages)
|
||||
|
||||
# 插入
|
||||
sql = '''
|
||||
INSERT INTO Users (Phone_number, Username, `Password`) \
|
||||
VALUES (%s, %s, %s); \
|
||||
'''
|
||||
try:
|
||||
cursor.execute(sql, (phone_number, username, password))
|
||||
conn.commit()
|
||||
return redirect(url_for('index'))
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
print(e)
|
||||
error_messages['mobileNo'] = "数据库异常,注册失败"
|
||||
return render_template('signup.html', errors=error_messages)
|
||||
finally:
|
||||
conn.close()
|
||||
20
Project/Service/func/utils.py
Normal file
20
Project/Service/func/utils.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from .config import db
|
||||
import pymysql
|
||||
from xpinyin import Pinyin
|
||||
from pymysql.cursors import Cursor
|
||||
|
||||
def get_cities():
|
||||
conn = pymysql.connect(**db)
|
||||
cursor = conn.cursor(pymysql.cursors.DictCursor)
|
||||
cursor.execute("SELECT DISTINCT City FROM Airports")
|
||||
cities = [row['City'] for row in cursor.fetchall()]
|
||||
cursor.close()
|
||||
conn.close()
|
||||
p = Pinyin()
|
||||
cities = [
|
||||
(row, p.get_pinyin(row).replace("-", ""))
|
||||
for row in cities
|
||||
]
|
||||
cities = sorted(cities, key=lambda x: x[1])
|
||||
cities = [row[0] for row in cities]
|
||||
return cities
|
||||
98
Project/Service/main.py
Normal file
98
Project/Service/main.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from flask import Flask, redirect, url_for, session, g
|
||||
from flask_httpauth import HTTPTokenAuth
|
||||
from flask_cors import CORS
|
||||
from func.config import db, SECRET_KEY
|
||||
import pymysql
|
||||
|
||||
import func.login
|
||||
import func.signup
|
||||
import func.modify
|
||||
import func.index
|
||||
import func.search
|
||||
import func.book
|
||||
import func.pay_confirm
|
||||
import func.cancel_order
|
||||
import func.order
|
||||
import func.order_list
|
||||
|
||||
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 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 = session.get('user_id')
|
||||
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("/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", methods=['GET', 'POST'])
|
||||
def modify():
|
||||
return func.modify.modify()
|
||||
|
||||
@app.route("/search", methods=['GET'])
|
||||
def search():
|
||||
return func.search.search()
|
||||
|
||||
@app.route("/book", methods=['GET', 'POST'])
|
||||
def book():
|
||||
return func.book.book()
|
||||
|
||||
@app.route("/order", methods=['GET'])
|
||||
def order():
|
||||
return func.order.order()
|
||||
|
||||
@app.route('/cancel_order', methods=['POST'])
|
||||
def cancel_order():
|
||||
return func.cancel_order.cancel_order()
|
||||
|
||||
@app.route('/pay_confirm', methods=['POST'])
|
||||
def pay_confirm():
|
||||
return func.pay_confirm.pay_confirm()
|
||||
|
||||
@app.route("/order_list", methods=['GET', 'POST'])
|
||||
def order_list():
|
||||
return func.order_list.order_list()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(
|
||||
host="0.0.0.0",
|
||||
port=8888,
|
||||
debug=True
|
||||
)
|
||||
12
Project/Service/requirements.txt
Normal file
12
Project/Service/requirements.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
blinker==1.8.1
|
||||
cffi==1.16.0
|
||||
click==8.1.7
|
||||
configparser==7.0.0
|
||||
cryptography==42.0.6
|
||||
Flask==3.0.3
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.3
|
||||
MarkupSafe==2.1.5
|
||||
pycparser==2.22
|
||||
PyMySQL==1.1.0
|
||||
Werkzeug==3.0.2
|
||||
3
Project/Service/run.sh
Normal file
3
Project/Service/run.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
SECRET_KEY='ILOVEDATABASETECH' FLASK_APP=main FLASK_ENV=development flask run \
|
||||
--host 0.0.0.0 \
|
||||
--port 5000 # default 5000
|
||||
238
Project/Service/static/css/book.css
Normal file
238
Project/Service/static/css/book.css
Normal file
@@ -0,0 +1,238 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.nav-buttons a:hover {
|
||||
background-color: #155a8c;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
padding-top: 80px; /* 给主内容增加顶部填充,以避免被固定导航栏遮挡 */
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: #1c6cb2;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.flight-row {
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease, border-radius 0.3s ease;
|
||||
}
|
||||
|
||||
.flight-row:hover {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 10px rgba(28, 108, 178, 0.5);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.flight-info th, .flight-info td {
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
form {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.passenger {
|
||||
margin-bottom: 20px;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease, border-radius 0.3s ease;
|
||||
}
|
||||
|
||||
.passenger:hover {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 10px rgba(28, 108, 178, 0.5);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.passenger h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #ff4d4d;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
label {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
text-align: right;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
input, select {
|
||||
flex: 2;
|
||||
padding: 8px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.total-price {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
background-color: #1c6cb2;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #155a8c;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-top: auto; /* 将footer推到页面底部 */
|
||||
}
|
||||
237
Project/Service/static/css/index.css
Normal file
237
Project/Service/static/css/index.css
Normal file
@@ -0,0 +1,237 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.nav-buttons a:hover {
|
||||
background-color: #155a8c;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
margin: 20px auto;
|
||||
padding-top: 80px; /* 给主内容增加顶部填充,以避免被固定导航栏遮挡 */
|
||||
}
|
||||
|
||||
.slides {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 620px;
|
||||
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;
|
||||
width: 80%;
|
||||
max-width: 800px;
|
||||
margin: -170px auto 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tabcontent {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
justify-content: flex-start; /* Left-align the form items */
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-row label {
|
||||
flex: 0 0 120px; /* Fixed width for labels */
|
||||
margin-right: 10px;
|
||||
text-align: right;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.form-row input,
|
||||
.form-row select {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.form-row.form-row-center {
|
||||
justify-content: center; /* Center-align the button */
|
||||
}
|
||||
|
||||
.passenger-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.passenger-input button {
|
||||
padding: 5px 10px;
|
||||
font-size: 18px;
|
||||
background-color: #1c6cb2;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-top: auto; /* 将footer推到页面底部 */
|
||||
}
|
||||
121
Project/Service/static/css/login.css
Normal file
121
Project/Service/static/css/login.css
Normal file
@@ -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;
|
||||
}
|
||||
149
Project/Service/static/css/modify.css
Normal file
149
Project/Service/static/css/modify.css
Normal file
@@ -0,0 +1,149 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.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;
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
padding-top: 80px; /* 给主内容增加顶部填充,以避免被固定导航栏遮挡 */
|
||||
}
|
||||
|
||||
.content {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
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%;
|
||||
position: relative;
|
||||
margin-top: auto; /* 将footer推到页面底部 */
|
||||
}
|
||||
191
Project/Service/static/css/order.css
Normal file
191
Project/Service/static/css/order.css
Normal file
@@ -0,0 +1,191 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.nav-buttons a:hover {
|
||||
background-color: #155a8c;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
padding-top: 80px; /* 给主内容增加顶部填充,以避免被固定导航栏遮挡 */
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: #1c6cb2;
|
||||
}
|
||||
|
||||
.order-info, .passenger-info, .order-summary {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.order-info p, .order-summary p {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
background-color: #1c6cb2;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #155a8c;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #ff4d4d;
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
background-color: #cc0000;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background-color: #f2f2f2;
|
||||
color: black;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-top: auto; /* 将footer推到页面底部 */
|
||||
}
|
||||
151
Project/Service/static/css/order_list.css
Normal file
151
Project/Service/static/css/order_list.css
Normal file
@@ -0,0 +1,151 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.nav-buttons a:hover {
|
||||
background-color: #155a8c;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
padding-top: 80px; /* 给主内容增加顶部填充,以避免被固定导航栏遮挡 */
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: #1c6cb2;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-top: auto; /* 将footer推到页面底部 */
|
||||
}
|
||||
|
||||
.order-row {
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease, border-radius 0.3s ease;
|
||||
}
|
||||
|
||||
.order-row:hover {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 10px rgba(28, 108, 178, 0.5);
|
||||
border-radius: 10px;
|
||||
}
|
||||
251
Project/Service/static/css/search.css
Normal file
251
Project/Service/static/css/search.css
Normal file
@@ -0,0 +1,251 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.nav-buttons a:hover {
|
||||
background-color: #155a8c;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
padding-top: 80px; /* 给主内容增加顶部填充,以避免被固定导航栏遮挡 */
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: #1c6cb2;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
color: red;
|
||||
font-size: 18px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: rgba(28, 108, 178, 0.9);
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-top: auto; /* 将footer推到页面底部 */
|
||||
}
|
||||
|
||||
.content {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
width: 80%;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tabcontent {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
justify-content: flex-start; /* Left-align the form items */
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-row label {
|
||||
flex: 0 0 120px; /* Fixed width for labels */
|
||||
margin-right: 10px;
|
||||
text-align: right;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.form-row input,
|
||||
.form-row select {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.form-row.form-row-center {
|
||||
justify-content: center; /* Center-align the button */
|
||||
}
|
||||
|
||||
.passenger-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.passenger-input button {
|
||||
padding: 5px 10px;
|
||||
font-size: 18px;
|
||||
background-color: #1c6cb2;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Add animations for flight rows */
|
||||
.flight-row {
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease, border-radius 0.3s ease;
|
||||
}
|
||||
|
||||
.flight-row:hover {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 10px rgba(28, 108, 178, 0.5);
|
||||
border-radius: 10px;
|
||||
}
|
||||
71
Project/Service/static/css/signup.css
Normal file
71
Project/Service/static/css/signup.css
Normal file
@@ -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;
|
||||
}
|
||||
15
Project/Service/static/js/checkInfo.js
Normal file
15
Project/Service/static/js/checkInfo.js
Normal file
@@ -0,0 +1,15 @@
|
||||
var checkInfo = {};
|
||||
|
||||
checkInfo.checkMobileNo = function() {
|
||||
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').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);
|
||||
}
|
||||
29
Project/Service/static/js/index.js
Normal file
29
Project/Service/static/js/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
81
Project/Service/static/js/login.js
Normal file
81
Project/Service/static/js/login.js
Normal file
@@ -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 = '自动登录失败';
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Project/Service/static/js/modify.js
Normal file
31
Project/Service/static/js/modify.js
Normal file
@@ -0,0 +1,31 @@
|
||||
var modify = {};
|
||||
|
||||
modify.showModifyPassword = function() {
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
if (modifyType === "修改密码") {
|
||||
for (let item of info.modifyPasswordLis) {
|
||||
item.style.display = 'block';
|
||||
}
|
||||
} 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';
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Project/Service/static/js/search.js
Normal file
42
Project/Service/static/js/search.js
Normal file
@@ -0,0 +1,42 @@
|
||||
function validateForm() {
|
||||
var departure = document.getElementById('departure').value;
|
||||
var destination = document.getElementById('destination').value;
|
||||
var warning = document.getElementById('destination-warning');
|
||||
if (departure === destination) {
|
||||
warning.textContent = '出发地和目的地不能相同';
|
||||
return false;
|
||||
} else {
|
||||
warning.textContent = '';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Set default date to tomorrow
|
||||
var departureDate = document.getElementById('departure-date');
|
||||
if (!departureDate.value) {
|
||||
var today = new Date();
|
||||
var tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
var month = ('0' + (tomorrow.getMonth() + 1)).slice(-2);
|
||||
var day = ('0' + tomorrow.getDate()).slice(-2);
|
||||
var year = tomorrow.getFullYear();
|
||||
departureDate.value = `${year}-${month}-${day}`;
|
||||
}
|
||||
});
|
||||
32
Project/Service/static/js/signup.js
Normal file
32
Project/Service/static/js/signup.js
Normal file
@@ -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 = '';
|
||||
}
|
||||
14
Project/Service/static/js/slideshow.js
Normal file
14
Project/Service/static/js/slideshow.js
Normal file
@@ -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();
|
||||
281
Project/Service/templates/book.html
Normal file
281
Project/Service/templates/book.html
Normal file
@@ -0,0 +1,281 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>航班预定</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/book.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<div class="logo">KJF航班订票</div>
|
||||
<div class="nav-buttons">
|
||||
<a href="{{ url_for('index') }}">首页</a>
|
||||
<a href="{{ url_for('order_list') }}">我的订单</a>
|
||||
</div>
|
||||
<div class="user-menu">
|
||||
<span>{{ username }}</span>
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">▼</button>
|
||||
<div class="dropdown-content">
|
||||
<a href="{{ url_for('modify') }}">修改账户信息</a>
|
||||
<a href="{{ url_for('logout') }}">退出登录</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h2>预定航班</h2>
|
||||
<div class="flight-info">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>航班号</th>
|
||||
<th>航空公司</th>
|
||||
<th>出发机场</th>
|
||||
<th>到达机场</th>
|
||||
<th>出发时间</th>
|
||||
<th>到达时间</th>
|
||||
<th>头等舱剩余座位</th>
|
||||
<th>商务舱剩余座位</th>
|
||||
<th>经济舱剩余座位</th>
|
||||
<th>头等舱价格</th>
|
||||
<th>商务舱价格</th>
|
||||
<th>经济舱价格</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="flight-row">
|
||||
<td>{{ flight.ID }}</td>
|
||||
<td>{{ flight.Airline }}</td>
|
||||
<td>{{ flight.Departure_airport_name }}</td>
|
||||
<td>{{ flight.Arrival_airport_name }}</td>
|
||||
<td>{{ flight.Departure_time }}</td>
|
||||
<td>{{ flight.Arrival_time }}</td>
|
||||
<td id="first-class-seats">{{ flight.First_class_seats_remaining }}</td>
|
||||
<td id="business-class-seats">{{ flight.Business_class_seats_remaining }}</td>
|
||||
<td id="economy-class-seats">{{ flight.Economy_class_seats_remaining }}</td>
|
||||
<td id="first-class-price">{{ flight.First_class_price }}</td>
|
||||
<td id="business-class-price">{{ flight.Business_class_price }}</td>
|
||||
<td id="economy-class-price">{{ flight.Economy_class_price }}</td>
|
||||
<td>{{ flight.Status }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<form id="booking-form" method="post" action="{{ url_for('book', flight_id=flight.ID) }}" onsubmit="return validateBookingForm()">
|
||||
<div id="passenger-list">
|
||||
<div class="passenger" data-index="0">
|
||||
<h3>乘机人 1</h3>
|
||||
<button type="button" class="delete-btn" onclick="removePassenger(this)">删除</button>
|
||||
<div class="form-row">
|
||||
<label for="card-code-0">身份证号:</label>
|
||||
<input type="text" id="card-code-0" name="passengers[0][card_code]" oninput="validateCardCode(this)">
|
||||
<div class="error-message" id="card-code-error-0"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="name-0">真实姓名:</label>
|
||||
<input type="text" id="name-0" name="passengers[0][name]">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="phone-number-0">手机号:</label>
|
||||
<input type="text" id="phone-number-0" name="passengers[0][phone_number]" oninput="validatePhoneNumber(this)">
|
||||
<div class="error-message" id="phone-number-error-0"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="seat-class-0">座位级别:</label>
|
||||
<select id="seat-class-0" name="passengers[0][seat_class]" onchange="updateSeatCount(this)">
|
||||
<option value="" disabled selected>请选择座位级别</option>
|
||||
<option value="First Class">头等舱</option>
|
||||
<option value="Business Class">商务舱</option>
|
||||
<option value="Economy Class">经济舱</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="total-price">
|
||||
总价: <span id="total-price">0</span> 元
|
||||
</div>
|
||||
<button type="button" class="btn" onclick="addPassenger()">添加乘机人</button>
|
||||
<button type="submit" class="btn">预定</button>
|
||||
</form>
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2024 KJF航班订票. 保留所有权利。</p>
|
||||
</footer>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize seat counts
|
||||
var passengerCount = 1;
|
||||
var firstClassSeats = {{ flight.First_class_seats_remaining }};
|
||||
var businessClassSeats = {{ flight.Business_class_seats_remaining }};
|
||||
var economyClassSeats = {{ flight.Economy_class_seats_remaining }};
|
||||
var firstClassPrice = parseFloat(document.getElementById("first-class-price").textContent);
|
||||
var businessClassPrice = parseFloat(document.getElementById("business-class-price").textContent);
|
||||
var economyClassPrice = parseFloat(document.getElementById("economy-class-price").textContent);
|
||||
|
||||
window.addPassenger = function() {
|
||||
passengerCount++;
|
||||
const passengerList = document.getElementById('passenger-list');
|
||||
const passengerDiv = document.createElement('div');
|
||||
passengerDiv.classList.add('passenger');
|
||||
passengerDiv.setAttribute('data-index', passengerCount - 1);
|
||||
|
||||
const disabledFirstClass = firstClassSeats <= 0 ? 'disabled' : '';
|
||||
const disabledBusinessClass = businessClassSeats <= 0 ? 'disabled' : '';
|
||||
const disabledEconomyClass = economyClassSeats <= 0 ? 'disabled' : '';
|
||||
|
||||
passengerDiv.innerHTML = `
|
||||
<h3>乘机人 ${passengerCount}</h3>
|
||||
<button type="button" class="delete-btn" onclick="removePassenger(this)">删除</button>
|
||||
<div class="form-row">
|
||||
<label for="card-code-${passengerCount - 1}">身份证号:</label>
|
||||
<input type="text" id="card-code-${passengerCount - 1}" name="passengers[${passengerCount - 1}][card_code]" oninput="validateCardCode(this)">
|
||||
<div class="error-message" id="card-code-error-${passengerCount - 1}"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="name-${passengerCount - 1}">真实姓名:</label>
|
||||
<input type="text" id="name-${passengerCount - 1}" name="passengers[${passengerCount - 1}][name]">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="phone-number-${passengerCount - 1}">手机号:</label>
|
||||
<input type="text" id="phone-number-${passengerCount - 1}" name="passengers[${passengerCount - 1}][phone_number]" oninput="validatePhoneNumber(this)">
|
||||
<div class="error-message" id="phone-number-error-${passengerCount - 1}"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="seat-class-${passengerCount - 1}">座位级别:</label>
|
||||
<select id="seat-class-${passengerCount - 1}" name="passengers[${passengerCount - 1}][seat_class]" onchange="updateSeatCount(this)">
|
||||
<option value="" disabled selected>请选择座位级别</option>
|
||||
<option value="First Class" ${disabledFirstClass}>头等舱</option>
|
||||
<option value="Business Class" ${disabledBusinessClass}>商务舱</option>
|
||||
<option value="Economy Class" ${disabledEconomyClass}>经济舱</option>
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
|
||||
passengerList.appendChild(passengerDiv);
|
||||
};
|
||||
|
||||
window.removePassenger = function(button) {
|
||||
const passengerDiv = button.parentElement;
|
||||
const index = passengerDiv.getAttribute('data-index');
|
||||
const seatClassSelect = document.getElementById(`seat-class-${index}`);
|
||||
const seatClass = seatClassSelect.value;
|
||||
|
||||
if (seatClass === 'First Class') firstClassSeats++;
|
||||
else if (seatClass === 'Business Class') businessClassSeats++;
|
||||
else if (seatClass === 'Economy Class') economyClassSeats++;
|
||||
|
||||
passengerDiv.remove();
|
||||
updateSeatDisplay();
|
||||
calculateTotalPrice();
|
||||
};
|
||||
|
||||
window.validateCardCode = function(input) {
|
||||
const 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]$/;
|
||||
const errorDiv = document.getElementById(`card-code-error-${input.id.split('-')[2]}`);
|
||||
if (!regexCardCode.test(input.value)) {
|
||||
errorDiv.textContent = '身份证号格式不正确';
|
||||
} else {
|
||||
errorDiv.textContent = '';
|
||||
}
|
||||
};
|
||||
|
||||
window.validatePhoneNumber = function(input) {
|
||||
const regexMobileNo = /^1[3-9]\d{9}$/;
|
||||
const errorDiv = document.getElementById(`phone-number-error-${input.id.split('-')[2]}`);
|
||||
if (!regexMobileNo.test(input.value)) {
|
||||
errorDiv.textContent = '手机号格式不正确';
|
||||
} else {
|
||||
errorDiv.textContent = '';
|
||||
}
|
||||
};
|
||||
|
||||
window.updateSeatCount = function(select) {
|
||||
const index = select.id.split('-')[2];
|
||||
const previousClass = select.getAttribute("data-previous-class");
|
||||
const newClass = select.value;
|
||||
|
||||
if (previousClass) {
|
||||
if (previousClass === 'First Class') firstClassSeats++;
|
||||
else if (previousClass === 'Business Class') businessClassSeats++;
|
||||
else if (previousClass === 'Economy Class') economyClassSeats++;
|
||||
}
|
||||
|
||||
if (newClass === 'First Class') {
|
||||
if (firstClassSeats <= 0) {
|
||||
alert("头等舱座位已满");
|
||||
select.value = "";
|
||||
return;
|
||||
}
|
||||
firstClassSeats--;
|
||||
} else if (newClass === 'Business Class') {
|
||||
if (businessClassSeats <= 0) {
|
||||
alert("商务舱座位已满");
|
||||
select.value = "";
|
||||
return;
|
||||
}
|
||||
businessClassSeats--;
|
||||
} else if (newClass === 'Economy Class') {
|
||||
if (economyClassSeats <= 0) {
|
||||
alert("经济舱座位已满");
|
||||
select.value = "";
|
||||
return;
|
||||
}
|
||||
economyClassSeats--;
|
||||
}
|
||||
|
||||
select.setAttribute("data-previous-class", newClass);
|
||||
updateSeatDisplay();
|
||||
calculateTotalPrice();
|
||||
};
|
||||
|
||||
function updateSeatDisplay() {
|
||||
document.getElementById("first-class-seats").textContent = firstClassSeats;
|
||||
document.getElementById("business-class-seats").textContent = businessClassSeats;
|
||||
document.getElementById("economy-class-seats").textContent = economyClassSeats;
|
||||
}
|
||||
|
||||
function calculateTotalPrice() {
|
||||
let totalPrice = 0;
|
||||
const seatClassSelects = document.querySelectorAll("select[id^='seat-class-']");
|
||||
seatClassSelects.forEach(select => {
|
||||
if (select.value === 'First Class') totalPrice += firstClassPrice;
|
||||
else if (select.value === 'Business Class') totalPrice += businessClassPrice;
|
||||
else if (select.value === 'Economy Class') totalPrice += economyClassPrice;
|
||||
});
|
||||
document.getElementById("total-price").textContent = totalPrice.toFixed(2);
|
||||
}
|
||||
|
||||
window.validateBookingForm = function() {
|
||||
// Validate seat availability
|
||||
if (firstClassSeats < 0 || businessClassSeats < 0 || economyClassSeats < 0) {
|
||||
alert("预定的座位数不能超过余座数");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate all card codes and phone numbers
|
||||
const cardCodes = document.querySelectorAll("input[id^='card-code-']");
|
||||
const phoneNumbers = document.querySelectorAll("input[id^='phone-number-']");
|
||||
for (let cardCode of cardCodes) {
|
||||
validateCardCode(cardCode);
|
||||
if (document.getElementById(`card-code-error-${cardCode.id.split('-')[2]}`).textContent !== '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (let phoneNumber of phoneNumbers) {
|
||||
validatePhoneNumber(phoneNumber);
|
||||
if (document.getElementById(`phone-number-error-${phoneNumber.id.split('-')[2]}`).textContent !== '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
146
Project/Service/templates/index.html
Normal file
146
Project/Service/templates/index.html
Normal file
@@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KJF航班订票</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/index.js') }}" defer></script>
|
||||
<script src="https://cdn.bootcss.com/blueimp-md5/2.12.0/js/md5.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<div class="logo">KJF航班订票</div>
|
||||
<div class="nav-buttons">
|
||||
<a href="{{ url_for('index') }}">首页</a>
|
||||
<a href="{{ url_for('order_list') }}">我的订单</a>
|
||||
</div>
|
||||
<div class="user-menu">
|
||||
<span>{{ username }}</span>
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">▼</button>
|
||||
<div class="dropdown-content">
|
||||
<a href="{{ url_for('modify') }}">修改账户信息</a>
|
||||
<a href="{{ url_for('logout') }}">退出登录</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="slides">
|
||||
<ul id="slide-container">
|
||||
{% for image in images %}
|
||||
<li>
|
||||
<a href="{{ image.link }}" target="_blank">
|
||||
<img src="{{ image.src }}">
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div id="ticket" class="tabcontent" style="display: block;">
|
||||
<form action="{{ url_for('search') }}" method="get" class="search-form" onsubmit="return validateForm()">
|
||||
<div class="form-row">
|
||||
<label for="departure">出发地:</label>
|
||||
<select id="departure" name="departure">
|
||||
{% for city in cities %}
|
||||
<option value="{{ city }}" {% if city == "北京" %}selected{% endif %}>{{ city }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="destination">目的地:</label>
|
||||
<select id="destination" name="destination">
|
||||
{% for city in cities %}
|
||||
<option value="{{ city }}" {% if city == "上海" %}selected{% endif %}>{{ city }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div id="destination-warning" class="error-message"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="departure-date">出发日期:</label>
|
||||
<input type="date" id="departure-date" name="departure-date" required>
|
||||
<div id="date-warning" class="error-message"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="passengers">乘客人数:</label>
|
||||
<div class="passenger-input">
|
||||
<button type="button" onclick="decrement()">-</button>
|
||||
<input type="number" id="passengers" name="passengers" value="1" min="1" max="50">
|
||||
<button type="button" onclick="increment()">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row form-row-center">
|
||||
<button type="submit" class="btn">立即查询</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2024 KJF航班订票. 保留所有权利。</p>
|
||||
</footer>
|
||||
<script src="{{ url_for('static', filename='js/slideshow.js') }}"></script>
|
||||
<script>
|
||||
function validateForm() {
|
||||
var departure = document.getElementById('departure').value;
|
||||
var destination = document.getElementById('destination').value;
|
||||
var warning = document.getElementById('destination-warning');
|
||||
var dateWarning = document.getElementById('date-warning');
|
||||
var departureDate = document.getElementById('departure-date').value;
|
||||
|
||||
var today = new Date();
|
||||
var selectedDate = new Date(departureDate);
|
||||
today.setHours(0, 0, 0, 0); // Ensure time comparison is not affected
|
||||
|
||||
if (departure === destination) {
|
||||
warning.textContent = '出发地和目的地不能相同';
|
||||
return false;
|
||||
} else {
|
||||
warning.textContent = '';
|
||||
}
|
||||
|
||||
if (selectedDate < today) {
|
||||
dateWarning.textContent = '出发日期不能早于今天';
|
||||
return false;
|
||||
} else {
|
||||
dateWarning.textContent = '';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Set default date to tomorrow
|
||||
var departureDate = document.getElementById('departure-date');
|
||||
var today = new Date();
|
||||
var tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
var month = ('0' + (tomorrow.getMonth() + 1)).slice(-2);
|
||||
var day = ('0' + tomorrow.getDate()).slice(-2);
|
||||
var year = tomorrow.getFullYear();
|
||||
departureDate.value = `${year}-${month}-${day}`;
|
||||
departureDate.setAttribute('min', `${year}-${month}-${day}`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
47
Project/Service/templates/login.html
Normal file
47
Project/Service/templates/login.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>我的航班订票官网</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/login.js') }}" defer></script>
|
||||
<script src="https://cdn.bootcss.com/blueimp-md5/2.12.0/js/md5.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>KJF航班订票</h1>
|
||||
</header>
|
||||
<main>
|
||||
<div class="slides">
|
||||
<ul id="slide-container">
|
||||
{% for image in images %}
|
||||
<li>
|
||||
<a href="{{ image.link }}" target="_blank">
|
||||
<img src="{{ image.src }}">
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>尽享精彩出行服务</h2>
|
||||
<div class="login-form">
|
||||
<input type="text" id="mobileNo" placeholder="手机号">
|
||||
<div id="mobileNoError" class="error-message"></div>
|
||||
<input type="password" id="password" placeholder="密码">
|
||||
<div id="loginError" class="error-message"></div>
|
||||
<input type="hidden" id="encryptedPassword">
|
||||
<div class="buttons">
|
||||
<button onclick="submitForm()">登录</button>
|
||||
<a class="btn" href="{{ url_for('signup') }}">注册</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2024 KJF航班订票. 保留所有权利。</p>
|
||||
</footer>
|
||||
<script src="{{ url_for('static', filename='js/slideshow.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
133
Project/Service/templates/modify.html
Normal file
133
Project/Service/templates/modify.html
Normal file
@@ -0,0 +1,133 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>修改账户</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/modify.css') }}">
|
||||
<script src="https://cdn.bootcss.com/blueimp-md5/2.12.0/js/md5.min.js"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
var message = "";
|
||||
{% for msg in messages %}
|
||||
message += "{{ msg }}\n";
|
||||
{% endfor %}
|
||||
alert(message);
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<a href="{{ url_for('index') }}" class="btn-back">返回主页</a>
|
||||
<h1>KJF航班订票</h1>
|
||||
<div class="header-spacer"></div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="content">
|
||||
<h2>修改账户信息</h2>
|
||||
<form action="#" method="post" id="modify-form">
|
||||
<div class="tab">
|
||||
<button type="button" class="tablinks active" onclick="openTab(event, 'deleteAccount')">删除账户</button>
|
||||
<button type="button" class="tablinks" onclick="openTab(event, 'modifyPassword')">修改密码</button>
|
||||
<button type="button" class="tablinks" onclick="openTab(event, 'modifyPhone')">修改手机号</button>
|
||||
<button type="button" class="tablinks" onclick="openTab(event, 'modifyUsername')">修改用户名</button>
|
||||
</div>
|
||||
<input type="hidden" id="modifyType" name="modifyType" value="删除账户">
|
||||
<div class="form-group">
|
||||
<div>登陆密码:</div>
|
||||
<input type="password" id="password" placeholder="请输入您的密码" required>
|
||||
<input id="encryptedPassword" name="encryptedPassword" type="hidden">
|
||||
</div>
|
||||
<div id="deleteAccount" class="tabcontent">
|
||||
<p>删除账户将无法恢复,请确认。</p>
|
||||
</div>
|
||||
<div id="modifyPassword" class="tabcontent" style="display:none">
|
||||
<div class="form-group">
|
||||
<div>新密码:</div>
|
||||
<input type="password" id="newPassword" placeholder="6-20位字母、数字或符号">
|
||||
<input id="encryptedNewPassword" name="encryptedNewPassword" type="hidden">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>确认密码:</div>
|
||||
<input type="password" id="confirmPassword" placeholder="再次输入您的新密码">
|
||||
</div>
|
||||
</div>
|
||||
<div id="modifyPhone" class="tabcontent" style="display:none">
|
||||
<p>原手机号为:{{ current_user_phone }}</p>
|
||||
<div class="form-group">
|
||||
<div>新手机号:</div>
|
||||
<input type="text" id="mobileNo" name="mobileNo" value title="手机号码" aria-label="手机号码" maxlength="11" placeholder="11位手机号">
|
||||
</div>
|
||||
</div>
|
||||
<div id="modifyUsername" class="tabcontent" style="display:none">
|
||||
<p>原用户名为:{{ current_username }}</p>
|
||||
<div class="form-group">
|
||||
<div>新用户名:</div>
|
||||
<input type="text" id="username" name="username" placeholder="请输入新的用户名">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" onclick="return submitForm()">确认</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2024 KJF航班订票. 保留所有权利。</p>
|
||||
</footer>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/checkInfo.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/modify.js') }}"></script>
|
||||
<script>
|
||||
function submitForm() {
|
||||
document.getElementById('encryptedPassword').value = md5(
|
||||
document.getElementById('password').value
|
||||
);
|
||||
|
||||
let modifyType = document.querySelector('.tablinks.active').textContent;
|
||||
document.getElementById('modifyType').value = modifyType;
|
||||
|
||||
if (modifyType === '删除账户') {
|
||||
return true;
|
||||
} else if (modifyType === '修改密码') {
|
||||
document.getElementById('encryptedNewPassword').value = md5(
|
||||
document.getElementById('newPassword').value
|
||||
);
|
||||
return checkInfo.checkNewPassword();
|
||||
} else if (modifyType === '修改手机号') {
|
||||
return checkInfo.checkMobileNo();
|
||||
} else if (modifyType === '修改用户名') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let activeTab = document.querySelector('.tablinks.active');
|
||||
if (activeTab) {
|
||||
openTab({currentTarget: activeTab}, activeTab.getAttribute('onclick').split("'")[1]);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
83
Project/Service/templates/order.html
Normal file
83
Project/Service/templates/order.html
Normal file
@@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>订单详情</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/order.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<div class="logo">KJF航班订票</div>
|
||||
<div class="nav-buttons">
|
||||
<a href="{{ url_for('index') }}">首页</a>
|
||||
<a href="{{ url_for('order_list') }}">我的订单</a>
|
||||
</div>
|
||||
<div class="user-menu">
|
||||
<span>{{ username }}</span>
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">▼</button>
|
||||
<div class="dropdown-content">
|
||||
<a href="{{ url_for('modify') }}">修改账户信息</a>
|
||||
<a href="{{ url_for('logout') }}">退出登录</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<button onclick="location.href='{{ url_for('order_list') }}'" class="back-btn">返回订单列表</button>
|
||||
<h2>订单详情</h2>
|
||||
<div class="order-info">
|
||||
<h3>航班信息</h3>
|
||||
<p>航班号: {{ order.FlightID }}</p>
|
||||
<p>航空公司: {{ order.Airline }}</p>
|
||||
<p>出发机场: {{ order.Departure_airport_name }}</p>
|
||||
<p>到达机场: {{ order.Arrival_airport_name }}</p>
|
||||
<p>出发时间: {{ order.Departure_time }}</p>
|
||||
<p>状态: {{ order.Status }}</p>
|
||||
</div>
|
||||
<div class="passenger-info">
|
||||
<h3>乘机人信息</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>身份证号</th>
|
||||
<th>姓名</th>
|
||||
<th>手机号</th>
|
||||
<th>座位级别</th>
|
||||
<th>票价</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ticket in tickets %}
|
||||
<tr>
|
||||
<td>{{ ticket.PassengerID }}</td>
|
||||
<td>{{ ticket.Name }}</td>
|
||||
<td>{{ ticket.Phone_number }}</td>
|
||||
<td>{{ ticket.Seat_class }}</td>
|
||||
<td>{{ ticket.Price }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="order-summary">
|
||||
<p>订单总价: {{ total_price }}</p>
|
||||
<p>支付状态: {% if order.Paid %}已支付{% else %}未支付{% endif %}</p>
|
||||
</div>
|
||||
<form method="post" action="{{ url_for('cancel_order', order_id=order.ID) }}">
|
||||
<button type="submit" class="btn cancel-btn">取消订单</button>
|
||||
</form>
|
||||
{% if not order.Paid %}
|
||||
<form method="post" action="{{ url_for('pay_confirm', order_id=order.ID) }}">
|
||||
<button type="submit" class="btn pay-btn">支付订单</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2024 KJF航班订票. 保留所有权利。</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
72
Project/Service/templates/order_list.html
Normal file
72
Project/Service/templates/order_list.html
Normal file
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>我的订单</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/order_list.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<div class="logo">KJF航班订票</div>
|
||||
<div class="nav-buttons">
|
||||
<a href="{{ url_for('index') }}">首页</a>
|
||||
<a href="{{ url_for('order_list') }}">我的订单</a>
|
||||
</div>
|
||||
<div class="user-menu">
|
||||
<span>{{ username }}</span>
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">▼</button>
|
||||
<div class="dropdown-content">
|
||||
<a href="{{ url_for('modify') }}">修改账户信息</a>
|
||||
<a href="{{ url_for('logout') }}">退出登录</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<h2>我的订单</h2>
|
||||
<div class="order-list">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>航班号</th>
|
||||
<th>航空公司</th>
|
||||
<th>出发机场</th>
|
||||
<th>到达机场</th>
|
||||
<th>出发时间</th>
|
||||
<th>状态</th>
|
||||
<th>乘机人</th>
|
||||
<th>订单总价</th>
|
||||
<th>支付状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in orders %}
|
||||
<tr class="order-row" onclick="location.href='{{ url_for('order', order_id=order.OrderID) }}'">
|
||||
<td>{{ order.FlightID }}</td>
|
||||
<td>{{ order.Airline }}</td>
|
||||
<td>{{ order.Departure_airport_name }}</td>
|
||||
<td>{{ order.Arrival_airport_name }}</td>
|
||||
<td>{{ order.Departure_time }}</td>
|
||||
<td>{{ order.Status }}</td>
|
||||
<td>
|
||||
{% for passenger in order.Passengers %}
|
||||
{{ passenger }}<br>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ order.TotalPrice }}</td>
|
||||
<td>{% if order.Paid %}已支付{% else %}未支付{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2024 KJF航班订票. 保留所有权利。</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
179
Project/Service/templates/search.html
Normal file
179
Project/Service/templates/search.html
Normal file
@@ -0,0 +1,179 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>航班搜索结果</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/search.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<div class="logo">KJF航班订票</div>
|
||||
<div class="nav-buttons">
|
||||
<a href="{{ url_for('index') }}">首页</a>
|
||||
<a href="{{ url_for('order_list') }}">我的订单</a>
|
||||
</div>
|
||||
<div class="user-menu">
|
||||
<span>{{ username }}</span>
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">▼</button>
|
||||
<div class="dropdown-content">
|
||||
<a href="{{ url_for('modify') }}">修改账户信息</a>
|
||||
<a href="{{ url_for('logout') }}">退出登录</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="content">
|
||||
<div id="ticket" class="tabcontent" style="display: block;">
|
||||
<form action="{{ url_for('search') }}" method="get" class="search-form" onsubmit="return validateForm()">
|
||||
<div class="form-row">
|
||||
<label for="departure">出发地:</label>
|
||||
<select id="departure" name="departure">
|
||||
{% for city in cities %}
|
||||
<option value="{{ city }}" {% if city == request.args.get('departure') %}selected{% endif %}>{{ city }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="destination">目的地:</label>
|
||||
<select id="destination" name="destination">
|
||||
{% for city in cities %}
|
||||
<option value="{{ city }}" {% if city == request.args.get('destination') %}selected{% endif %}>{{ city }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div id="destination-warning" class="error-message"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="departure-date">出发日期:</label>
|
||||
<input type="date" id="departure-date" name="departure-date" value="{{ request.args.get('departure-date', '') }}" required>
|
||||
<div id="date-warning" class="error-message"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="passengers">乘客人数:</label>
|
||||
<div class="passenger-input">
|
||||
<button type="button" onclick="decrement()">-</button>
|
||||
<input type="number" id="passengers" name="passengers" value="{{ request.args.get('passengers', 1) }}" min="1" max="50">
|
||||
<button type="button" onclick="increment()">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row form-row-center">
|
||||
<button type="submit" class="btn">立即查询</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>航班搜索结果</h2>
|
||||
{% if flights %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>航班号</th>
|
||||
<th>航空公司</th>
|
||||
<th>出发机场</th>
|
||||
<th>到达机场</th>
|
||||
<th>出发时间</th>
|
||||
<th>到达时间</th>
|
||||
<th>头等舱剩余座位</th>
|
||||
<th>商务舱剩余座位</th>
|
||||
<th>经济舱剩余座位</th>
|
||||
<th>头等舱价格</th>
|
||||
<th>商务舱价格</th>
|
||||
<th>经济舱价格</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for flight in flights %}
|
||||
<tr class="flight-row" onclick="window.location.href='{{ url_for('book', flight_id=flight.ID) }}'">
|
||||
<td>{{ flight.ID }}</td>
|
||||
<td>{{ flight.Airline }}</td>
|
||||
<td>{{ flight.Departure_airport_name }}</td>
|
||||
<td>{{ flight.Arrival_airport_name }}</td>
|
||||
<td>{{ flight.Departure_time }}</td>
|
||||
<td>{{ flight.Arrival_time }}</td>
|
||||
<td>{{ flight.First_class_seats_remaining }}</td>
|
||||
<td>{{ flight.Business_class_seats_remaining }}</td>
|
||||
<td>{{ flight.Economy_class_seats_remaining }}</td>
|
||||
<td>{{ flight.First_class_price }}</td>
|
||||
<td>{{ flight.Business_class_price }}</td>
|
||||
<td>{{ flight.Economy_class_price }}</td>
|
||||
<td>{{ flight.Status }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="no-results">没有找到符合条件的航班。</p>
|
||||
{% endif %}
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2024 KJF航班订票. 保留所有权利。</p>
|
||||
</footer>
|
||||
<script src="{{ url_for('static', filename='js/search.js') }}"></script>
|
||||
<script>
|
||||
function validateForm() {
|
||||
var departure = document.getElementById('departure').value;
|
||||
var destination = document.getElementById('destination').value;
|
||||
var warning = document.getElementById('destination-warning');
|
||||
var dateWarning = document.getElementById('date-warning');
|
||||
var departureDate = document.getElementById('departure-date').value;
|
||||
|
||||
var today = new Date();
|
||||
var selectedDate = new Date(departureDate);
|
||||
today.setHours(0, 0, 0, 0); // Ensure time comparison is not affected
|
||||
|
||||
if (departure === destination) {
|
||||
warning.textContent = '出发地和目的地不能相同';
|
||||
return false;
|
||||
} else {
|
||||
warning.textContent = '';
|
||||
}
|
||||
|
||||
if (selectedDate < today) {
|
||||
dateWarning.textContent = '出发日期不能早于今天';
|
||||
return false;
|
||||
} else {
|
||||
dateWarning.textContent = '';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Set default date to tomorrow
|
||||
var departureDate = document.getElementById('departure-date');
|
||||
if (!departureDate.value) {
|
||||
var today = new Date();
|
||||
var tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
var month = ('0' + (tomorrow.getMonth() + 1)).slice(-2);
|
||||
var day = ('0' + tomorrow.getDate()).slice(-2);
|
||||
var year = tomorrow.getFullYear();
|
||||
departureDate.value = `${year}-${month}-${day}`;
|
||||
departureDate.setAttribute('min', `${year}-${month}-${day}`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
51
Project/Service/templates/signup.html
Normal file
51
Project/Service/templates/signup.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>注册 - KJF航班订票</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/signup.css') }}">
|
||||
<script src="https://cdn.bootcss.com/blueimp-md5/2.12.0/js/md5.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/checkInfo.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/signup.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
<form action="#" method="post" id="signupForm">
|
||||
<ul>
|
||||
<li>
|
||||
<div>
|
||||
<div>用户名:</div>
|
||||
<input type="text" id="username" name="username" placeholder="请输入用户名" required>
|
||||
<div class="error-message">{{ errors.username }}</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>手机号:</div>
|
||||
<input type="text" id="mobileNo" name="mobileNo" placeholder="请输入手机号" maxlength="11" required>
|
||||
<div class="error-message" id="mobileNoError">{{ errors.mobileNo }}</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>登录密码:</div>
|
||||
<input type="password" id="password" placeholder="6-20位字母、数字或符号" required>
|
||||
<input id="encryptedPassword" name="encryptedPassword" type="hidden">
|
||||
<div class="error-message" id="passwordError">{{ errors.password }}</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>确认密码:</div>
|
||||
<input type="password" id="confirmPassword" placeholder="再次输入您的登录密码" required>
|
||||
<input id="encryptedConfirmPassword" name="encryptedConfirmPassword" type="hidden">
|
||||
<div class="error-message" id="confirmPasswordError">{{ errors.confirmPassword }}</div>
|
||||
</div>
|
||||
</li>
|
||||
<div>
|
||||
<button type="submit" onclick="return submitForm()">注册</button>
|
||||
</div>
|
||||
</ul>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user