价格计算策略更新
This commit is contained in:
parent
127a2734ef
commit
adbe039e3d
12
.idea/dataSources.xml
generated
Normal file
12
.idea/dataSources.xml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="tg01@localhost" uuid="3d275efa-2b35-4106-ba1e-b855e94a8dfa">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://localhost:3306/tg01</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
@ -20,6 +20,7 @@ from .routers import user_coupon
|
||||
from .routers import admin_message
|
||||
from .routers import user_messages
|
||||
from .routers import bell
|
||||
from .routers import admin_strategy
|
||||
|
||||
|
||||
|
||||
@ -61,6 +62,7 @@ app.include_router(admin_coupon.router, prefix="/admin", tags=["Admin-Coupon"])
|
||||
|
||||
app.include_router(admin_message.router, prefix="/admin", tags=["Admin-Message"])
|
||||
|
||||
app.include_router(admin_strategy.router, prefix="/admin", tags=["Admin-Strategy"])
|
||||
|
||||
|
||||
|
||||
|
||||
57
backend/app/routers/admin_strategy.py
Normal file
57
backend/app/routers/admin_strategy.py
Normal file
@ -0,0 +1,57 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
from ..services.admin_strategy_service import (
|
||||
get_table_strategy_list,
|
||||
create_table_strategy,
|
||||
delete_table_strategy,
|
||||
bind_table_to_strategy
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class get_list(BaseModel):
|
||||
token: str
|
||||
|
||||
class StrategyData(BaseModel):
|
||||
strategy_name: str
|
||||
segment1_threshold: int
|
||||
segment1_price: float
|
||||
segment2_threshold: int
|
||||
segment2_price: float
|
||||
segment3_price: float
|
||||
description: str | None = None
|
||||
segment3_threshold: int | None = None
|
||||
|
||||
class create_strategy(BaseModel):
|
||||
token: str
|
||||
strategy_data: StrategyData
|
||||
|
||||
class delete_strategy(BaseModel):
|
||||
token: str
|
||||
strategy_id: int
|
||||
class update_strategy(BaseModel):
|
||||
token: str
|
||||
table_ids: List[int]
|
||||
strategy_id: int
|
||||
|
||||
|
||||
@router.post("/table-strategies/list")
|
||||
def api_get_table_strategy_list(request: get_list):
|
||||
return get_table_strategy_list(request.token)
|
||||
|
||||
|
||||
@router.post("/table-strategies/create")
|
||||
def api_create_table_strategy(request: create_strategy):
|
||||
return create_table_strategy(request.token, request.strategy_data.model_dump())
|
||||
|
||||
|
||||
@router.post("/table-strategies/delete")
|
||||
def api_delete_table_strategy(request: delete_strategy):
|
||||
return delete_table_strategy(request.token, request.strategy_id)
|
||||
|
||||
|
||||
@router.post("/table-strategies/bind")
|
||||
def api_bind_table_to_strategy(request: update_strategy):
|
||||
return bind_table_to_strategy(request.token, request.strategy_id, request.table_ids)
|
||||
|
||||
@ -3,16 +3,17 @@ from pydantic import BaseModel
|
||||
class TableCreate(BaseModel):
|
||||
game_table_number: str
|
||||
capacity: int
|
||||
price: float
|
||||
strategy_id: int
|
||||
strategy_name: str
|
||||
|
||||
class TableCreateRequest(BaseModel):
|
||||
token: str
|
||||
game_table_number: str
|
||||
capacity: int
|
||||
price: float
|
||||
strategy_id: int
|
||||
|
||||
class TableResponse(BaseModel):
|
||||
table_id: int
|
||||
game_table_number: str
|
||||
capacity: int
|
||||
price: float
|
||||
strategy_name: str
|
||||
|
||||
145
backend/app/services/admin_strategy_service.py
Normal file
145
backend/app/services/admin_strategy_service.py
Normal file
@ -0,0 +1,145 @@
|
||||
from fastapi import HTTPException
|
||||
from ..db import get_connection
|
||||
from ..utils.jwt_handler import verify_token
|
||||
|
||||
def _verify_admin_permission(token: str):
|
||||
"""公共权限验证方法"""
|
||||
try:
|
||||
payload = verify_token(token)
|
||||
username = payload["sub"]
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=401, detail=str(e))
|
||||
|
||||
connection = get_connection()
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
cursor.execute("SELECT user_type FROM users WHERE username = %s;", (username,))
|
||||
admin_user = cursor.fetchone()
|
||||
|
||||
if not admin_user or admin_user["user_type"] != "admin":
|
||||
raise HTTPException(status_code=403, detail="Permission denied")
|
||||
return username
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
def get_table_strategy_list(token: str):
|
||||
"""
|
||||
获取 table 的策略列表
|
||||
"""
|
||||
_verify_admin_permission(token)
|
||||
print("get_table_strategy_list")
|
||||
connection = get_connection()
|
||||
cursor = None
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
cursor.execute("SELECT * FROM table_pricing_strategies")
|
||||
strategies = cursor.fetchall()
|
||||
return strategies
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
def create_table_strategy(token: str, strategy_data: dict):
|
||||
"""
|
||||
创建 table 的策略
|
||||
"""
|
||||
_verify_admin_permission(token)
|
||||
connection = get_connection()
|
||||
cursor = None
|
||||
|
||||
required_fields = ['strategy_name', 'segment1_threshold', 'segment1_price',
|
||||
'segment2_threshold', 'segment2_price', 'segment3_price']
|
||||
missing_fields = [field for field in required_fields if field not in strategy_data]
|
||||
if missing_fields:
|
||||
raise HTTPException(status_code=400, detail=f"缺少必填字段: {', '.join(missing_fields)}")
|
||||
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
cursor.execute("""
|
||||
INSERT INTO table_pricing_strategies (
|
||||
strategy_name,
|
||||
description,
|
||||
segment1_threshold,
|
||||
segment1_price,
|
||||
segment2_threshold,
|
||||
segment2_price,
|
||||
segment3_price,
|
||||
segment3_threshold
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||
""", (
|
||||
strategy_data['strategy_name'],
|
||||
strategy_data.get('description'), # 可选字段
|
||||
strategy_data['segment1_threshold'],
|
||||
strategy_data['segment1_price'],
|
||||
strategy_data['segment2_threshold'],
|
||||
strategy_data['segment2_price'],
|
||||
strategy_data['segment3_price'],
|
||||
strategy_data.get('segment3_threshold') # 可选字段
|
||||
))
|
||||
connection.commit()
|
||||
return {"message": "Table 策略创建成功"}
|
||||
except Exception as e:
|
||||
connection.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
|
||||
def delete_table_strategy(token: str, strategy_id: int):
|
||||
"""
|
||||
删除 table 的策略
|
||||
"""
|
||||
_verify_admin_permission(token)
|
||||
connection = get_connection()
|
||||
cursor = None
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
# 检查当前策略是否被使用
|
||||
cursor.execute("SELECT COUNT(*) as count FROM game_tables WHERE table_pricing_strategy_id = %s", (strategy_id,))
|
||||
result = cursor.fetchone()
|
||||
if result['count'] > 0:
|
||||
raise HTTPException(status_code=400, detail="该策略正在被使用,不可删除")
|
||||
# 删除策略
|
||||
cursor.execute("DELETE FROM table_pricing_strategies WHERE strategy_id = %s", (strategy_id,))
|
||||
connection.commit()
|
||||
return {"message": "Table 策略删除成功"}
|
||||
except Exception as e:
|
||||
connection.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
def bind_table_to_strategy(token: str, strategy_id: int, table_ids: list):
|
||||
"""
|
||||
绑定桌子到指定策略
|
||||
"""
|
||||
_verify_admin_permission(token)
|
||||
connection = get_connection()
|
||||
cursor = None
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
# 批量更新桌子对应的策略
|
||||
update_query = "UPDATE game_tables SET table_pricing_strategy_id = %s WHERE table_id = %s"
|
||||
params = [(strategy_id, table_id) for table_id in table_ids]
|
||||
|
||||
cursor.executemany(update_query, params)
|
||||
connection.commit()
|
||||
return {"message": f"成功绑定{len(table_ids)}张桌子到指定策略"}
|
||||
except Exception as e:
|
||||
connection.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
@ -30,6 +30,9 @@ def create_table(token: str, table_data: dict) -> dict:
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# 设置默认价格倍率(原本的价格计算方法,已经弃用,防止报错)
|
||||
default_price = 1.00
|
||||
|
||||
# 检查桌号是否已存在
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) AS count FROM game_tables WHERE game_table_number = %s",
|
||||
@ -38,9 +41,18 @@ def create_table(token: str, table_data: dict) -> dict:
|
||||
if cursor.fetchone()['count'] > 0:
|
||||
raise HTTPException(status_code=400, detail="桌号已存在")
|
||||
|
||||
if 'strategy_id' in table_data and table_data['strategy_id']:
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) AS count FROM table_pricing_strategies WHERE strategy_id = %s",
|
||||
(table_data['strategy_id'],)
|
||||
)
|
||||
if cursor.fetchone()['count'] == 0:
|
||||
raise HTTPException(status_code=400, detail="指定的策略不存在")
|
||||
|
||||
cursor.execute(
|
||||
"INSERT INTO game_tables (game_table_number, capacity, price) VALUES (%s, %s, %s)",
|
||||
(table_data['game_table_number'], table_data['capacity'], table_data['price'])
|
||||
"INSERT INTO game_tables (game_table_number, capacity, price, table_pricing_strategy_id) VALUES (%s, %s, %s, %s)",
|
||||
(
|
||||
table_data['game_table_number'], table_data['capacity'], default_price, table_data.get('strategy_id'))
|
||||
)
|
||||
connection.commit()
|
||||
return {"message": "桌台创建成功"}
|
||||
@ -93,13 +105,18 @@ def update_table(token: str, table_id: int, table_data: dict) -> dict:
|
||||
if cursor.fetchone()['count'] > 0:
|
||||
raise HTTPException(status_code=400, detail="桌号已存在")
|
||||
|
||||
# 设置默认价格倍率(原本的价格计算方法,已经弃用,防止报错)
|
||||
default_price = 1.00
|
||||
cursor.execute(
|
||||
"""UPDATE game_tables SET
|
||||
game_table_number=%s,
|
||||
capacity=%s,
|
||||
price=%s
|
||||
price=%s,
|
||||
table_pricing_strategy_id=%s
|
||||
WHERE table_id=%s""",
|
||||
(table_data['game_table_number'], table_data['capacity'], table_data['price'], table_id)
|
||||
(
|
||||
table_data['game_table_number'], table_data['capacity'], default_price, table_data.get('strategy_id'),
|
||||
table_id)
|
||||
)
|
||||
if cursor.rowcount == 0:
|
||||
raise HTTPException(status_code=404, detail="桌台不存在")
|
||||
@ -119,10 +136,27 @@ def list_tables_service(token: str):
|
||||
cursor = None
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
cursor.execute("SELECT * FROM game_tables")
|
||||
# 修改查询语句,关联 pricing_strategies 表获取 strategy_name
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
gt.table_id,
|
||||
gt.game_table_number,
|
||||
gt.capacity,
|
||||
gt.price,
|
||||
gt.table_pricing_strategy_id,
|
||||
tps.strategy_name
|
||||
FROM
|
||||
game_tables gt
|
||||
JOIN
|
||||
table_pricing_strategies tps
|
||||
ON
|
||||
gt.table_pricing_strategy_id = tps.strategy_id;
|
||||
""")
|
||||
return cursor.fetchall()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
if cursor: cursor.close()
|
||||
if cursor:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ def check_table_occupancy_service(table_id: int):
|
||||
) AS is_occupied
|
||||
""", (table_id,))
|
||||
result = cursor.fetchone()
|
||||
return {"is_occupied": "false"} # 不检测桌子状态
|
||||
return {"is_occupied": 0} # 不检测桌子状态
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
|
||||
@ -44,25 +44,26 @@ def calculate_segmented_price(duration, strategy):
|
||||
if s3 is not None and duration > s3:
|
||||
duration = s3 # 限制最大计费时间
|
||||
price = (s1 * p1) + ((s2 - s1) * p2) + ((duration - s2) * p3)
|
||||
|
||||
return price
|
||||
|
||||
def calculate_overtime_fee(start_datetime, end_datetime):
|
||||
"""计算超时费用(深夜时段收费)"""
|
||||
"""计算超时费用(深夜时段收费,按整小时计费)"""
|
||||
# 读取配置文件
|
||||
config = configparser.ConfigParser()
|
||||
config.read('backend/config.conf')
|
||||
stay_up_late = config['stay_up_late']
|
||||
start_time = datetime.strptime(stay_up_late['start_time'], '%H:%M:%S').time()
|
||||
end_time = datetime.strptime(stay_up_late['end_time'], '%H:%M:%S').time()
|
||||
price_per_minute = Decimal(stay_up_late['price_minutes'])
|
||||
price_per_hour = Decimal(stay_up_late['price_minutes']) # 每小时收费
|
||||
|
||||
overtime_fee = Decimal(0)
|
||||
current_time = start_datetime
|
||||
current_time = start_datetime.replace(minute=0, second=0, microsecond=0) # 取整到整点
|
||||
|
||||
while current_time < end_datetime:
|
||||
while current_time + timedelta(hours=1) <= end_datetime:
|
||||
next_hour = current_time + timedelta(hours=1)
|
||||
if start_time <= current_time.time() or current_time.time() < end_time:
|
||||
overtime_fee += price_per_minute
|
||||
current_time += timedelta(minutes=1)
|
||||
overtime_fee += price_per_hour # 满一小时才计费
|
||||
current_time = next_hour # 跳到下一个整点
|
||||
|
||||
return overtime_fee
|
||||
|
||||
@ -100,22 +101,22 @@ def calculate_order_price(order_id):
|
||||
raise HTTPException(status_code=400, detail="价格策略未找到")
|
||||
|
||||
# 计算价格
|
||||
unit_price = calculate_segmented_price(duration_dec, pricing_strategy)
|
||||
# unit_price = calculate_segmented_price(duration_dec, pricing_strategy)
|
||||
# 原本的基础价格废用,桌子价格为主要逻辑
|
||||
table_price = calculate_segmented_price(duration_dec, table_strategy)
|
||||
|
||||
# 计算总价格
|
||||
base_price = (unit_price * order['num_players']) + table_price
|
||||
# base_price = (unit_price * order['num_players']) + table_price
|
||||
base_price = table_price * order['num_players']
|
||||
overtime_fee = calculate_overtime_fee(start_time, end_time)
|
||||
total_price = base_price + overtime_fee
|
||||
|
||||
# print("总价: ", unit_price * order['num_players'], " 桌费: ", table_price, " 时长: ", duration_dec, " 超时费: ", overtime_fee)
|
||||
# 更新订单价格
|
||||
cursor.execute("""
|
||||
UPDATE orders
|
||||
SET payable_price = %s,
|
||||
game_process_time = %s,
|
||||
overtime_fee = %s
|
||||
game_process_time = %s
|
||||
WHERE order_id = %s
|
||||
""", (total_price, duration_dec, overtime_fee, order_id))
|
||||
""", (total_price, duration_dec, order_id))
|
||||
|
||||
connection.commit()
|
||||
return True
|
||||
|
||||
@ -19,6 +19,23 @@ SQL_SCRIPT = """
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for announcements
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `announcements`;
|
||||
CREATE TABLE `announcements` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`text` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`start_time` datetime NOT NULL,
|
||||
`end_time` datetime NOT NULL,
|
||||
`color` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`created_at` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for coupons
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `coupons`;
|
||||
CREATE TABLE `coupons` (
|
||||
`coupon_id` int NOT NULL AUTO_INCREMENT,
|
||||
@ -26,31 +43,24 @@ CREATE TABLE `coupons` (
|
||||
`discount_type` enum('fixed','percentage') CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`discount_amount` decimal(10,2) DEFAULT NULL,
|
||||
`min_order_amount` decimal(10,2) DEFAULT NULL,
|
||||
`valid_from` date DEFAULT NULL,
|
||||
`valid_to` date DEFAULT NULL,
|
||||
`valid_from` datetime DEFAULT NULL,
|
||||
`valid_to` datetime DEFAULT NULL,
|
||||
`is_active` tinyint(1) DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`quantity` int DEFAULT NULL,
|
||||
PRIMARY KEY (`coupon_id`),
|
||||
UNIQUE KEY `coupon_code` (`coupon_code`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
DROP TABLE IF EXISTS `game_game_tags`;
|
||||
CREATE TABLE `game_game_tags` (
|
||||
`game_tag_relation_id` int NOT NULL AUTO_INCREMENT,
|
||||
`game_id` int DEFAULT NULL,
|
||||
`tag_id` int DEFAULT NULL,
|
||||
PRIMARY KEY (`game_tag_relation_id`),
|
||||
KEY `game_id` (`game_id`),
|
||||
KEY `tag_id` (`tag_id`),
|
||||
CONSTRAINT `game_game_tags_ibfk_1` FOREIGN KEY (`game_id`) REFERENCES `games` (`game_id`),
|
||||
CONSTRAINT `game_game_tags_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `game_tags` (`tag_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for game_groups
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `game_groups`;
|
||||
CREATE TABLE `game_groups` (
|
||||
`group_id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int DEFAULT NULL,
|
||||
`group_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`user_id` int NOT NULL,
|
||||
`group_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
|
||||
`start_date` date DEFAULT NULL,
|
||||
`start_time` datetime DEFAULT NULL,
|
||||
@ -59,49 +69,43 @@ CREATE TABLE `game_groups` (
|
||||
`group_status` enum('recruiting','full','completed','cancelled','pause') CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT 'recruiting',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`play_start_time` datetime DEFAULT NULL,
|
||||
`play_end_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for game_tables
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `game_tables`;
|
||||
CREATE TABLE `game_tables` (
|
||||
`table_id` int NOT NULL AUTO_INCREMENT COMMENT '服务器桌号',
|
||||
`game_table_number` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '物理桌号',
|
||||
`capacity` int NOT NULL COMMENT '承载人数',
|
||||
`price` decimal(10,2) NOT NULL COMMENT '价格倍率',
|
||||
PRIMARY KEY (`table_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
CREATE TABLE `announcement` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`text` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`start_time` datetime NOT NULL,
|
||||
`end_time` datetime NOT NULL,
|
||||
`color` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
CREATE TABLE `user_coupons` (
|
||||
`user_coupon_id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int NOT NULL,
|
||||
`coupon_id` int NOT NULL,
|
||||
`obtained_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`used_at` timestamp NULL DEFAULT NULL,
|
||||
`is_used` tinyint(1) DEFAULT '0',
|
||||
`valid_from` date DEFAULT NULL,
|
||||
`valid_to` date DEFAULT NULL,
|
||||
PRIMARY KEY (`user_coupon_id`),
|
||||
UNIQUE KEY `unique_user_coupon` (`user_id`,`coupon_id`),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`coupon_id`) REFERENCES `coupons` (`coupon_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
`table_pricing_strategy_id` int DEFAULT NULL,
|
||||
PRIMARY KEY (`table_id`),
|
||||
KEY `tables_price` (`table_pricing_strategy_id`),
|
||||
CONSTRAINT `tables_price` FOREIGN KEY (`table_pricing_strategy_id`) REFERENCES `table_pricing_strategies` (`strategy_id`) ON DELETE RESTRICT ON UPDATE RESTRICT
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for game_tags
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `game_tags`;
|
||||
CREATE TABLE `game_tags` (
|
||||
`tag_id` int NOT NULL AUTO_INCREMENT,
|
||||
`tag_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
PRIMARY KEY (`tag_id`),
|
||||
UNIQUE KEY `tag_name` (`tag_name`)
|
||||
`game_id` int NOT NULL,
|
||||
`tag_id` int NOT NULL,
|
||||
PRIMARY KEY (`game_id`,`tag_id`),
|
||||
KEY `tag_id` (`tag_id`),
|
||||
CONSTRAINT `game_tags_ibfk_1` FOREIGN KEY (`game_id`) REFERENCES `games` (`game_id`),
|
||||
CONSTRAINT `game_tags_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`tag_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for games
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `games`;
|
||||
CREATE TABLE `games` (
|
||||
`game_id` int NOT NULL AUTO_INCREMENT,
|
||||
`game_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
@ -109,17 +113,21 @@ CREATE TABLE `games` (
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
|
||||
`min_players` int DEFAULT NULL,
|
||||
`max_players` int DEFAULT NULL,
|
||||
`duration` int DEFAULT NULL,
|
||||
`duration` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
|
||||
`price` decimal(10,2) DEFAULT NULL,
|
||||
`difficulty_level` int DEFAULT NULL,
|
||||
`is_available` tinyint(1) DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`quantity` int DEFAULT NULL,
|
||||
`photo_url` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`photo_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`long_description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
|
||||
PRIMARY KEY (`game_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for group_members
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `group_members`;
|
||||
CREATE TABLE `group_members` (
|
||||
`group_member_id` int NOT NULL AUTO_INCREMENT,
|
||||
@ -130,8 +138,11 @@ CREATE TABLE `group_members` (
|
||||
PRIMARY KEY (`group_member_id`),
|
||||
KEY `group_id` (`group_id`),
|
||||
CONSTRAINT `group_members_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `game_groups` (`group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for order_coupons
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `order_coupons`;
|
||||
CREATE TABLE `order_coupons` (
|
||||
`order_coupon_id` int NOT NULL AUTO_INCREMENT,
|
||||
@ -144,6 +155,9 @@ CREATE TABLE `order_coupons` (
|
||||
CONSTRAINT `order_coupons_ibfk_2` FOREIGN KEY (`coupon_id`) REFERENCES `coupons` (`coupon_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for orders
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `orders`;
|
||||
CREATE TABLE `orders` (
|
||||
`order_id` int NOT NULL COMMENT '订单 id',
|
||||
@ -156,34 +170,96 @@ CREATE TABLE `orders` (
|
||||
`num_players` int NOT NULL COMMENT '游玩人数',
|
||||
`payable_price` decimal(10,2) DEFAULT NULL COMMENT '未优惠价格',
|
||||
`order_status` enum('pending','paid','in_progress','completed','cancelled') CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'pending' COMMENT '订单状态',
|
||||
`payment_method` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '优惠后价格',
|
||||
`payment_method` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '支付平台',
|
||||
`coupon_id` int DEFAULT NULL COMMENT '优惠卷 id',
|
||||
`used_points` int DEFAULT NULL COMMENT '使用积分',
|
||||
`paid_price` decimal(10,2) DEFAULT '0.00' COMMENT '优惠后价格',
|
||||
`game_process_time` int DEFAULT NULL COMMENT '游戏时长',
|
||||
`settlement_time` datetime DEFAULT NULL,
|
||||
`wx_transaction_id` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
|
||||
`out_trade_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`pricing_strategy_id` int NOT NULL COMMENT '绑定的价格策略 ID',
|
||||
PRIMARY KEY (`order_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `game_table_id` (`game_table_id`),
|
||||
KEY `game_id` (`game_id`),
|
||||
KEY `coupon_id` (`coupon_id`),
|
||||
KEY `orders_ibfk_4` (`pricing_strategy_id`),
|
||||
CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`),
|
||||
CONSTRAINT `orders_ibfk_2` FOREIGN KEY (`game_table_id`) REFERENCES `game_tables` (`table_id`),
|
||||
CONSTRAINT `orders_ibfk_3` FOREIGN KEY (`game_id`) REFERENCES `games` (`game_id`)
|
||||
CONSTRAINT `orders_ibfk_3` FOREIGN KEY (`game_id`) REFERENCES `games` (`game_id`),
|
||||
CONSTRAINT `orders_ibfk_4` FOREIGN KEY (`pricing_strategy_id`) REFERENCES `pricing_strategies` (`strategy_id`) ON DELETE RESTRICT ON UPDATE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for player_messages
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `player_messages`;
|
||||
CREATE TABLE `player_messages` (
|
||||
`message_id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int NOT NULL,
|
||||
`message_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`message_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `player_messages_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for player_reviews
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `player_reviews`;
|
||||
CREATE TABLE `player_reviews` (
|
||||
`review_id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int NOT NULL,
|
||||
`game_id` int NOT NULL,
|
||||
`rating` tinyint NOT NULL COMMENT '玩家的打分,范围可以根据实际情况设定,如 1 - 5 星',
|
||||
`comment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '玩家的评论内容,可以为空',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`review_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `game_id` (`game_id`),
|
||||
CONSTRAINT `player_reviews_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `player_reviews_ibfk_2` FOREIGN KEY (`game_id`) REFERENCES `games` (`game_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for points_history
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `points_history`;
|
||||
CREATE TABLE `points_history` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int NOT NULL,
|
||||
`change_amount` int NOT NULL,
|
||||
`reason` varchar(255) COLLATE utf8mb4_bin NOT NULL,
|
||||
`reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `points_history_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for pricing_strategies
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pricing_strategies`;
|
||||
CREATE TABLE `pricing_strategies` (
|
||||
`strategy_id` int NOT NULL AUTO_INCREMENT COMMENT '价格策略 ID',
|
||||
`strategy_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '策略名称',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '策略描述',
|
||||
`segment1_threshold` int NOT NULL COMMENT '第一阶段时间(分钟)',
|
||||
`segment1_price` decimal(10,2) NOT NULL COMMENT '第一阶段单价(元/人/分钟)',
|
||||
`segment2_threshold` int NOT NULL COMMENT '第二阶段时间(分钟)',
|
||||
`segment2_price` decimal(10,2) NOT NULL COMMENT '第二阶段单价(元/人/分钟)',
|
||||
`segment3_price` decimal(10,2) NOT NULL COMMENT '第三阶段单价(元/人/分钟)',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`segment3_threshold` int DEFAULT NULL COMMENT '第三阶段时间上限(分钟,可选)',
|
||||
PRIMARY KEY (`strategy_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for reviews
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `reviews`;
|
||||
CREATE TABLE `reviews` (
|
||||
`review_id` int NOT NULL AUTO_INCREMENT,
|
||||
@ -200,6 +276,77 @@ CREATE TABLE `reviews` (
|
||||
CONSTRAINT `reviews_ibfk_2` FOREIGN KEY (`game_id`) REFERENCES `games` (`game_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for table_pricing_strategies
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `table_pricing_strategies`;
|
||||
CREATE TABLE `table_pricing_strategies` (
|
||||
`strategy_id` int NOT NULL AUTO_INCREMENT COMMENT '桌费策略 ID',
|
||||
`strategy_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '策略名称',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '策略描述',
|
||||
`segment1_threshold` int NOT NULL COMMENT '第一阶段时间(分钟)',
|
||||
`segment1_price` decimal(10,2) NOT NULL COMMENT '第一阶段单价(元/分钟)',
|
||||
`segment2_threshold` int NOT NULL COMMENT '第二阶段时间(分钟)',
|
||||
`segment2_price` decimal(10,2) NOT NULL COMMENT '第二阶段单价(元/分钟)',
|
||||
`segment3_price` decimal(10,2) NOT NULL COMMENT '第三阶段单价(元/分钟)',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`segment3_threshold` int DEFAULT NULL COMMENT '第三阶段时间上限(分钟,可选)',
|
||||
PRIMARY KEY (`strategy_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for tags
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
CREATE TABLE `tags` (
|
||||
`tag_id` int NOT NULL AUTO_INCREMENT,
|
||||
`tag_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`tag_id`),
|
||||
UNIQUE KEY `tag_name` (`tag_name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_coupons
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `user_coupons`;
|
||||
CREATE TABLE `user_coupons` (
|
||||
`user_coupon_id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int NOT NULL,
|
||||
`coupon_id` int NOT NULL,
|
||||
`obtained_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`used_at` timestamp NULL DEFAULT NULL,
|
||||
`is_used` tinyint(1) DEFAULT '0',
|
||||
`valid_from` date DEFAULT NULL,
|
||||
`valid_to` date DEFAULT NULL,
|
||||
PRIMARY KEY (`user_coupon_id`),
|
||||
KEY `coupon_id` (`coupon_id`),
|
||||
KEY `idx_user_status` (`user_id`,`is_used`),
|
||||
CONSTRAINT `user_coupons_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `user_coupons_ibfk_2` FOREIGN KEY (`coupon_id`) REFERENCES `coupons` (`coupon_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_game_rating
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `user_game_rating`;
|
||||
CREATE TABLE `user_game_rating` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int NOT NULL,
|
||||
`game_id` int NOT NULL,
|
||||
`rating` tinyint(1) NOT NULL COMMENT '拉为 1,踩为 0',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `game_id` (`game_id`),
|
||||
CONSTRAINT `user_game_rating_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `user_game_rating_ibfk_2` FOREIGN KEY (`game_id`) REFERENCES `games` (`game_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for users
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
CREATE TABLE `users` (
|
||||
`user_id` int NOT NULL AUTO_INCREMENT,
|
||||
@ -212,11 +359,13 @@ CREATE TABLE `users` (
|
||||
`points` int DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`wx_openid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
PRIMARY KEY (`user_id`),
|
||||
UNIQUE KEY `phone_number` (`phone_number`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=62 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ def create_app():
|
||||
from frontend.routes.announcement import announcements_bp
|
||||
from frontend.routes.coupons import coupons_bp
|
||||
from frontend.routes.messages import messages_bp
|
||||
from frontend.routes.strategies import strategies_bp
|
||||
|
||||
app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False
|
||||
toolbar = DebugToolbarExtension(app)
|
||||
@ -34,6 +35,7 @@ def create_app():
|
||||
app.register_blueprint(announcements_bp)
|
||||
app.register_blueprint(coupons_bp)
|
||||
app.register_blueprint(messages_bp)
|
||||
app.register_blueprint(strategies_bp)
|
||||
|
||||
# 添加自定义过滤器
|
||||
@app.template_filter('datetime')
|
||||
|
||||
129
frontend/routes/strategies.py
Normal file
129
frontend/routes/strategies.py
Normal file
@ -0,0 +1,129 @@
|
||||
from flask import Blueprint, render_template, session, redirect, url_for, flash, request
|
||||
import requests
|
||||
from frontend.config import Config
|
||||
|
||||
strategies_bp = Blueprint('strategies', __name__)
|
||||
|
||||
@strategies_bp.route('/strategies')
|
||||
def list_strategies():
|
||||
if not session.get('token'):
|
||||
flash("请先登录", "warning")
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
token = session.get('token')
|
||||
try:
|
||||
resp = requests.post(
|
||||
f"{Config.BASE_API_URL}/admin/table-strategies/list",
|
||||
json={"token": token}
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
return render_template('strategies/list.html', strategies=resp.json())
|
||||
else:
|
||||
flash("获取策略列表失败", "danger")
|
||||
except Exception as e:
|
||||
flash(f"网络错误: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('dashboard.index'))
|
||||
|
||||
@strategies_bp.route('/strategies/create', methods=['POST'])
|
||||
def create_strategy():
|
||||
if not session.get('token'):
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
try:
|
||||
# 从JSON请求体获取数据
|
||||
request_data = request.get_json()
|
||||
strategy_data = request_data.get('strategy_data')
|
||||
print(strategy_data.get('strategy_name'))
|
||||
|
||||
# 添加数据验证
|
||||
if not strategy_data:
|
||||
flash("缺少策略数据", "danger")
|
||||
return redirect(url_for('strategies.list_strategies'))
|
||||
# 获取表单数据并转换为下划线格式
|
||||
form_data = {
|
||||
"strategy_name": strategy_data.get('strategy_name'),
|
||||
"segment1_threshold": int(strategy_data.get('segment1_threshold')),
|
||||
"segment1_price": float(strategy_data.get('segment1_price')),
|
||||
"segment2_threshold": int(strategy_data.get('segment2_threshold')),
|
||||
"segment2_price": float(strategy_data.get('segment2_price')),
|
||||
"segment3_price": float(strategy_data.get('segment3_price')),
|
||||
"description": strategy_data.get('description'),
|
||||
"segment3_threshold": int(strategy_data['segment3_threshold']) if strategy_data.get(
|
||||
'segment3_threshold') else None
|
||||
}
|
||||
|
||||
resp = requests.post(
|
||||
f"{Config.BASE_API_URL}/admin/table-strategies/create",
|
||||
json={
|
||||
"token": session['token'],
|
||||
"strategy_data": form_data
|
||||
}
|
||||
)
|
||||
|
||||
if resp.status_code == 200:
|
||||
flash("策略创建成功", "success")
|
||||
return {"status": "success"}, 200
|
||||
else:
|
||||
error_msg = resp.json().get('detail', '未知错误')
|
||||
flash(f"创建失败: {error_msg}", "danger")
|
||||
except ValueError as e:
|
||||
flash(f"数据类型错误: {str(e)}", "danger")
|
||||
except Exception as e:
|
||||
flash(f"网络错误: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('strategies.list_strategies'))
|
||||
|
||||
@strategies_bp.route('/strategies/delete/<int:strategy_id>', methods=['POST'])
|
||||
def delete_strategy(strategy_id):
|
||||
if not session.get('token'):
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
try:
|
||||
resp = requests.post(
|
||||
f"{Config.BASE_API_URL}/admin/table-strategies/delete",
|
||||
json={
|
||||
"token": session['token'],
|
||||
"strategy_id": strategy_id
|
||||
}
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
flash("策略删除成功", "success")
|
||||
return {"status": "success"}, 200
|
||||
else:
|
||||
error_msg = resp.json().get('detail', '未知错误')
|
||||
flash(f"删除失败: {error_msg}", "danger")
|
||||
except Exception as e:
|
||||
flash(f"网络错误: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('strategies.list_strategies'))
|
||||
|
||||
@strategies_bp.route('/strategies/bind_tables', methods=['POST'])
|
||||
def bind_tables():
|
||||
if not session.get('token'):
|
||||
return {'status': 'error', 'message': '请先登录'}, 401
|
||||
|
||||
data = request.get_json()
|
||||
strategy_id = data.get('strategy_id')
|
||||
table_ids = data.get('table_ids')
|
||||
|
||||
if not strategy_id or not table_ids:
|
||||
return {'status': 'error', 'message': '缺少必要参数'}, 400
|
||||
|
||||
try:
|
||||
resp = requests.post(
|
||||
f"{Config.BASE_API_URL}/admin/table-strategies/bind",
|
||||
json={
|
||||
"token": session['token'],
|
||||
"strategy_id": strategy_id,
|
||||
"table_ids": table_ids
|
||||
}
|
||||
)
|
||||
|
||||
if resp.status_code == 200:
|
||||
return {'status': 'success', 'message': '绑定成功'}
|
||||
else:
|
||||
error_msg = resp.json().get('detail', '未知错误')
|
||||
return {'status': 'error', 'message': f'绑定失败: {error_msg}'}, resp.status_code
|
||||
except Exception as e:
|
||||
return {'status': 'error', 'message': f'网络错误: {str(e)}'}, 500
|
||||
@ -18,6 +18,7 @@ def list_tables():
|
||||
if resp.status_code != 200:
|
||||
flash("获取桌台列表失败", "danger")
|
||||
return redirect(url_for('dashboard.index'))
|
||||
# print (resp.json())
|
||||
|
||||
return render_template('tables/list.html', tables=resp.json())
|
||||
except Exception as e:
|
||||
@ -29,13 +30,24 @@ def add_table():
|
||||
if not session.get('token'):
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
strategies = []
|
||||
try:
|
||||
strategy_resp = requests.post(
|
||||
f"{Config.BASE_API_URL}/admin/table-strategies/list",
|
||||
json={"token": session['token']}
|
||||
)
|
||||
strategies = strategy_resp.json() if strategy_resp.status_code == 200 else []
|
||||
except Exception as e:
|
||||
flash(f"获取策略列表失败: {str(e)}", "warning")
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
payload = {
|
||||
"token": session['token'],
|
||||
"game_table_number": str(request.form['game_table_number']),
|
||||
"capacity": int(request.form['capacity']),
|
||||
"price": float(request.form['price'])
|
||||
"strategy_id" : int(request.form['strategy_id'])
|
||||
}
|
||||
resp = requests.post(
|
||||
f"{Config.BASE_API_URL}/admin/tables/create",
|
||||
@ -54,20 +66,33 @@ def add_table():
|
||||
except Exception as e:
|
||||
flash(f"网络错误: {str(e)}", "danger")
|
||||
|
||||
return render_template('tables/add.html')
|
||||
return render_template('tables/add.html', strategies = strategies )
|
||||
|
||||
@tables_bp.route('/tables/edit/<int:table_id>', methods=['GET', 'POST'])
|
||||
def edit_table(table_id):
|
||||
if not session.get('token'):
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
# 获取策略列表
|
||||
strategies = []
|
||||
try:
|
||||
# 获取策略列表
|
||||
strategy_resp = requests.post(
|
||||
f"{Config.BASE_API_URL}/admin/table-strategies/list",
|
||||
json={"token": session['token']}
|
||||
)
|
||||
strategies = strategy_resp.json() if strategy_resp.status_code == 200 else []
|
||||
|
||||
except Exception as e:
|
||||
flash(f"获取策略列表失败: {str(e)}", "warning")
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
payload = {
|
||||
"token": session['token'],
|
||||
"game_table_number": str(request.form['game_table_number']),
|
||||
"capacity": int(request.form['capacity']),
|
||||
"price": float(request.form['price'])
|
||||
"strategy_id" : int(request.form['strategy_id'])
|
||||
}
|
||||
resp = requests.put(
|
||||
f"{Config.BASE_API_URL}/admin/tables/{table_id}",
|
||||
@ -96,7 +121,7 @@ def edit_table(table_id):
|
||||
tables = resp.json()
|
||||
table = next((t for t in tables if t['table_id'] == table_id), None)
|
||||
if table:
|
||||
return render_template('tables/edit.html', table=table)
|
||||
return render_template('tables/edit.html', table=table, strategies=strategies)
|
||||
flash("桌台不存在", "danger")
|
||||
except Exception as e:
|
||||
flash(f"获取数据失败: {str(e)}", "danger")
|
||||
@ -122,3 +147,18 @@ def delete_table(table_id):
|
||||
flash(f"网络错误: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('tables.list_tables'))
|
||||
|
||||
|
||||
@tables_bp.route('/admin/tables/list', methods=['GET'])
|
||||
def list_all_tables():
|
||||
if not session.get('token'):
|
||||
return {"detail": "未认证"}, 401
|
||||
|
||||
try:
|
||||
resp = requests.get(
|
||||
f"{Config.BASE_API_URL}/admin/tables",
|
||||
headers={"Authorization": f"Bearer {session['token']}"}
|
||||
)
|
||||
return resp.json()
|
||||
except Exception as e:
|
||||
return {"detail": str(e)}, 500
|
||||
@ -4,10 +4,16 @@
|
||||
<meta charset="utf-8">
|
||||
<title>{% block title %}桌游厅点单系统管理后台{% endblock %}</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; }
|
||||
.nav-link { margin-right: 10px; }
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.container,
|
||||
.container-lg,
|
||||
@ -17,8 +23,9 @@
|
||||
max-width: 2000px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 添加全局通知样式 */
|
||||
.global-notification {
|
||||
.global-notification {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
@ -33,76 +40,79 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
|
||||
<a class="navbar-brand" href="{{ url_for('dashboard.index') }}">桌游厅点单系统</a>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
{% if session.token %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('dashboard.index') }}">首页</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('users.list_users') }}">用户管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('orders.index') }}">订单管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('games.list_games') }}">游戏管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('tables.list_tables') }}">桌台管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('games.manage_tags') }}">游戏标签</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('groups.manage_groups') }}">拼团管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('announcements.manage_announcements') }}">公告管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('coupons.list_coupons') }}">优惠券</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('messages.list_messages') }}">留言管理</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
|
||||
<a class="navbar-brand" href="{{ url_for('dashboard.index') }}">桌游厅点单系统</a>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
{% if session.token %}
|
||||
<span class="navbar-text">
|
||||
<a href="{{ url_for('auth.logout') }}" class="btn btn-outline-danger">退出登录</a>
|
||||
</span>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('dashboard.index') }}">首页</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('users.list_users') }}">用户管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('orders.index') }}">订单管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('games.list_games') }}">游戏管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('tables.list_tables') }}">桌台管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('games.manage_tags') }}">游戏标签</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('groups.manage_groups') }}">拼团管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('announcements.manage_announcements') }}">公告管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('coupons.list_coupons') }}">优惠券</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('messages.list_messages') }}">留言管理</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('strategies.list_strategies') }}">策略管理</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="关闭">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</ul>
|
||||
{% if session.token %}
|
||||
<span class="navbar-text">
|
||||
<a class="btn btn-outline-danger" href="{{ url_for('auth.logout') }}">退出登录</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- jQuery 和 Bootstrap JS -->
|
||||
<!-- 1) 替换为完整版本的 jQuery,而非 slim -->
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</nav>
|
||||
<div class="container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button aria-label="关闭" class="close" data-dismiss="alert" type="button">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<!-- jQuery 和 Bootstrap JS -->
|
||||
<!-- 1) 替换为完整版本的 jQuery,而非 slim -->
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
<script>
|
||||
// 建立全局WebSocket连接
|
||||
const adminWS = new WebSocket(`ws://192.168.5.16:8000/bell/ws/admin`);
|
||||
|
||||
// 处理接收到的消息
|
||||
adminWS.onmessage = function(event) {
|
||||
adminWS.onmessage = function (event) {
|
||||
const tableNumber = event.data;
|
||||
// 显示通知
|
||||
if (Notification.permission === "granted") {
|
||||
@ -141,7 +151,7 @@
|
||||
}, 10000);
|
||||
}
|
||||
</script>
|
||||
<!-- 2) 新增 scripts block,供子模板插入自定义脚本 -->
|
||||
{% block scripts %}{% endblock %}
|
||||
<!-- 2) 新增 scripts block,供子模板插入自定义脚本 -->
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
398
frontend/templates/strategies/list.html
Normal file
398
frontend/templates/strategies/list.html
Normal file
@ -0,0 +1,398 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}收款策略管理{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>收款策略管理</h2>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">创建新策略</div>
|
||||
<div class="card-body">
|
||||
<form id="strategyForm" onsubmit="submitStrategy(event)">
|
||||
<!-- 策略基本信息 -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label>策略名称 <span class="text-danger">*</span></label>
|
||||
<input type="text" id="strategyName" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<label>策略描述</label>
|
||||
<textarea id="description" class="form-control" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 价格分段设置 -->
|
||||
<div class="border-top pt-3">
|
||||
<h5 class="text-muted mb-3">价格分段设置</h5>
|
||||
|
||||
<!-- 第一阶段 -->
|
||||
<div class="row alert alert-light">
|
||||
<div class="col-md-3">
|
||||
<label>第一阶段 <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<input type="number" id="segment1Threshold" class="form-control" placeholder="分钟"
|
||||
required>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">分钟内</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>单价 <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<input type="number" step="0.01" id="segment1Price" class="form-control" required>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">元/分钟</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第二阶段 -->
|
||||
<div class="row alert alert-light">
|
||||
<div class="col-md-3">
|
||||
<label>第二阶段 <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<input type="number" id="segment2Threshold" class="form-control" placeholder="分钟"
|
||||
required>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">分钟内</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>单价 <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<input type="number" step="0.01" id="segment2Price" class="form-control" required>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">元/分钟</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第三阶段 -->
|
||||
<div class="row alert alert-light">
|
||||
<div class="col-md-3">
|
||||
<label>第三阶段(可选)</label>
|
||||
<div class="input-group">
|
||||
<input type="number" id="segment3Threshold" class="form-control" placeholder="分钟">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">分钟后</span>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted">填写后超过该部分的价格将不计算</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>固定单价 <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<input type="number" step="0.01" id="segment3Price" class="form-control" required>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">元/分钟</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mt-3">创建策略</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>策略ID</th>
|
||||
<th>策略名称</th>
|
||||
<th>分段1阈值</th>
|
||||
<th>分段1价格</th>
|
||||
<th>分段2阈值</th>
|
||||
<th>分段2价格</th>
|
||||
<th>分段3阈值</th>
|
||||
<th>分段3价格</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for strategy in strategies %}
|
||||
<tr>
|
||||
<td>{{ strategy.strategy_id }}</td>
|
||||
<td>{{ strategy.strategy_name }}</td>
|
||||
<td>{{ strategy.segment1_threshold }}分钟</td>
|
||||
<td>{{ strategy.segment1_price }}元</td>
|
||||
<td>{{ strategy.segment2_threshold }}分钟</td>
|
||||
<td>{{ strategy.segment2_price }}元</td>
|
||||
<td>{{ strategy.segment3_threshold or '无' }}分钟</td>
|
||||
<td>{{ strategy.segment3_price or '无' }}元</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-danger"
|
||||
onclick="deleteStrategy({{ strategy.strategy_id }})">删除
|
||||
</button>
|
||||
<button class="btn btn-sm btn-info"
|
||||
onclick="openBindTableModal({{ strategy.strategy_id }})">绑定桌子
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 绑定桌子模态框 -->
|
||||
<!-- 修改后的绑定桌子模态框 -->
|
||||
<div class="modal fade" id="bindTableModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form id="bindTableForm" onsubmit="submitBindTable(event)">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">绑定桌子</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<div class="table-checkbox-list" style="max-height: 300px; overflow-y: auto;">
|
||||
<!-- 这里会通过JavaScript动态加载桌子列表 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-success">绑定选中桌子</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
let currentStrategyId = null;
|
||||
|
||||
async function submitStrategy(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const payload = {
|
||||
strategy_name: document.getElementById('strategyName').value,
|
||||
description: document.getElementById('description').value || null,
|
||||
segment1_threshold: parseInt(document.getElementById('segment1Threshold').value),
|
||||
segment1_price: parseFloat(document.getElementById('segment1Price').value),
|
||||
segment2_threshold: parseInt(document.getElementById('segment2Threshold').value),
|
||||
segment2_price: parseFloat(document.getElementById('segment2Price').value),
|
||||
segment3_price: parseFloat(document.getElementById('segment3Price').value),
|
||||
segment3_threshold: document.getElementById('segment3Threshold').value
|
||||
? parseInt(document.getElementById('segment3Threshold').value)
|
||||
: null
|
||||
};
|
||||
|
||||
// 添加数据验证
|
||||
if (payload.segment3_threshold !== null && payload.segment3_threshold <= payload.segment2_threshold) {
|
||||
alert("第三阶段阈值必须大于第二阶段");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = '/strategies/create';
|
||||
|
||||
const resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: "{{ session.token }}",
|
||||
strategy_data: payload
|
||||
})
|
||||
});
|
||||
|
||||
const result = await resp.json();
|
||||
if (result.status === "success") {
|
||||
alert("创建成功");
|
||||
window.location.reload(); // 手动刷新页面
|
||||
} else {
|
||||
alert(`失败: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求失败:', error);
|
||||
alert('网络请求异常,请检查连接');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteStrategy(strategyId) {
|
||||
if (!confirm('确定删除该策略?')) return;
|
||||
|
||||
try {
|
||||
const resp = await fetch(`/strategies/delete/${strategyId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${"{{ session.token }}"}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: "{{ session.token }}"
|
||||
})
|
||||
});
|
||||
|
||||
if (resp.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await resp.json();
|
||||
alert(`删除失败: ${error.detail}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求失败:', error);
|
||||
alert('网络请求异常,请检查连接');
|
||||
}
|
||||
}
|
||||
|
||||
// ... existing code ...
|
||||
|
||||
async function openBindTableModal(strategyId) {
|
||||
currentStrategyId = strategyId;
|
||||
try {
|
||||
const resp = await fetch('/admin/tables/list', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${"{{ session.token }}"}`
|
||||
}
|
||||
});
|
||||
const tables = await resp.json();
|
||||
|
||||
// 清空并重新渲染桌子列表
|
||||
const container = document.querySelector('.table-checkbox-list');
|
||||
container.innerHTML = tables.map(table => `
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
name="table_ids"
|
||||
value="${table.table_id}"
|
||||
id="table_${table.table_id}">
|
||||
<label class="form-check-label" for="table_${table.table_id}">
|
||||
${table.game_table_number}
|
||||
</label>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
$('#bindTableModal').modal('show');
|
||||
} catch (error) {
|
||||
alert('获取桌子列表失败');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function submitBindTable(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const tableIds = Array.from(document.querySelectorAll('#bindTableForm input[name="table_ids"]:checked')).map(input => input.value);
|
||||
|
||||
try {
|
||||
const resp = await fetch('/strategies/bind_tables', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
strategy_id: currentStrategyId,
|
||||
table_ids: tableIds.map(id => parseInt(id))
|
||||
})
|
||||
});
|
||||
|
||||
const result = await resp.json();
|
||||
if (resp.ok) {
|
||||
alert(result.message);
|
||||
$('#bindTableModal').modal('hide');
|
||||
} else {
|
||||
alert(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求失败:', error);
|
||||
alert('网络请求异常,请检查连接');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 初始化图表
|
||||
let chart = null;
|
||||
|
||||
function generateChartData() {
|
||||
const s1Threshold = parseInt(document.getElementById('segment1Threshold').value) || 0;
|
||||
const s1Price = parseFloat(document.getElementById('segment1Price').value) || 0;
|
||||
const s2Threshold = parseInt(document.getElementById('segment2Threshold').value) || s1Threshold + 30;
|
||||
const s2Price = parseFloat(document.getElementById('segment2Price').value) || 0;
|
||||
const s3Threshold = parseInt(document.getElementById('segment3Threshold').value);
|
||||
const s3Price = parseFloat(document.getElementById('segment3Price').value) || 0;
|
||||
|
||||
// 确定X轴范围
|
||||
const maxX = s3Threshold ? s3Threshold + 10 : s2Threshold + 10;
|
||||
const dataPoints = [];
|
||||
|
||||
// 生成价格点
|
||||
for (let x = 0; x <= maxX; x++) {
|
||||
let price;
|
||||
if (x <= s1Threshold) {
|
||||
price = s1Price;
|
||||
} else if (x <= s2Threshold) {
|
||||
price = s2Price;
|
||||
} else {
|
||||
price = s3Price || s2Price;
|
||||
}
|
||||
dataPoints.push({x, price});
|
||||
}
|
||||
|
||||
return {
|
||||
labels: dataPoints.map(p => p.x + '分钟'),
|
||||
datasets: [{
|
||||
label: '单价 (元/分钟)',
|
||||
data: dataPoints.map(p => p.price),
|
||||
borderColor: 'rgb(75, 192, 192)',
|
||||
tension: 0.1
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
function updateChart() {
|
||||
const data = generateChartData();
|
||||
|
||||
if (chart) {
|
||||
chart.destroy();
|
||||
}
|
||||
|
||||
chart = new Chart(document.getElementById('priceChart'), {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: '价格 (元)'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: '使用时长'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加输入监听
|
||||
const inputs = document.querySelectorAll('#strategyForm input[type="number"]');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('input', () => {
|
||||
if (document.getElementById('strategyName').value) {
|
||||
updateChart();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 初始化时创建图表
|
||||
document.addEventListener('DOMContentLoaded', updateChart);
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
{% endblock %}
|
||||
@ -7,18 +7,32 @@
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label>桌号</label>
|
||||
<input type="text" name="game_table_number" class="form-control" required >
|
||||
<input type="text" name="game_table_number" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>容量(人数)</label>
|
||||
<input type="number" name="capacity" class="form-control" required min="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>价格倍率</label>
|
||||
<input type="number" name="price" class="form-control" step="0.01" required min="1">
|
||||
<label>收款策略 <span class="text-danger">*</span></label>
|
||||
<select name="strategy_id" class="form-control" required style="white-space: normal;">
|
||||
<option value="">请选择策略...</option>
|
||||
{% for strategy in strategies %}
|
||||
<option value="{{ strategy.strategy_id }}" style="white-space: normal;">
|
||||
{{ strategy.strategy_name }} - {{ strategy.description }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">提交</button>
|
||||
<a href="{{ url_for('tables.list_tables') }}" class="btn btn-secondary">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.select-wrapper select option {
|
||||
white-space: normal !important;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<div class="form-group">
|
||||
<label>桌号</label>
|
||||
<input type="text" name="game_table_number" class="form-control"
|
||||
value="{{ table.game_table_number }}" required >
|
||||
value="{{ table.game_table_number }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>容量(人数)</label>
|
||||
@ -16,12 +16,26 @@
|
||||
value="{{ table.capacity }}" required min="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>价格倍率</label>
|
||||
<input type="number" name="price" class="form-control"
|
||||
value="{{ table.price }}" step="0.01" required min="1">
|
||||
<label>收款策略 <span class="text-danger">*</span></label>
|
||||
<select name="strategy_id" class="form-control" required style="white-space: normal;">
|
||||
<option value="">请选择策略...</option>
|
||||
{% for strategy in strategies %}
|
||||
<option value="{{ strategy.strategy_id }}"
|
||||
{% if table.table_pricing_strategy_id== strategy.strategy_id %}selected{% endif %}
|
||||
style="white-space: normal;">
|
||||
{{ strategy.strategy_name }} - {{ strategy.description }}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{{ url_for('tables.list_tables') }}" class="btn btn-secondary">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.select2-container--bootstrap-5 .select2-results__option {
|
||||
white-space: normal !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -1,40 +1,133 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}桌台管理{% endblock %}
|
||||
{% block title %}台桌管理{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>桌台管理</h2>
|
||||
<a href="{{ url_for('tables.add_table') }}" class="btn btn-primary mb-3">添加新桌台</a>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th>
|
||||
<th>桌号</th>
|
||||
<th>容量</th>
|
||||
<th>价格倍率</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for table in tables %}
|
||||
<tr>
|
||||
<td>{{ table.table_id }}</td>
|
||||
<td>{{ table.game_table_number }}</td>
|
||||
<td>{{ table.capacity }}人</td>
|
||||
<td>{{ "%.2f"|format(table.price) }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('tables.edit_table', table_id=table.table_id) }}"
|
||||
class="btn btn-sm btn-warning">编辑</a>
|
||||
<form action="{{ url_for('tables.delete_table', table_id=table.table_id) }}"
|
||||
method="POST" class="d-inline">
|
||||
<button type="submit" class="btn btn-sm btn-danger"
|
||||
onclick="return confirm('确认删除该桌台?')">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<h1>桌台列表</h1>
|
||||
<a href="{{ url_for('tables.add_table') }}" class="btn btn-primary mb-3">添加桌台</a>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>桌号</th>
|
||||
<th>容量</th>
|
||||
<th>收款策略</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for table in tables %}
|
||||
<tr>
|
||||
<td>{{ table.table_id }}</td>
|
||||
<td>{{ table.game_table_number }}</td>
|
||||
<td>{{ table.capacity }}</td>
|
||||
<td>{{ table.strategy_name }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('tables.edit_table', table_id=table.table_id) }}"
|
||||
class="btn btn-warning btn-sm">编辑</a>
|
||||
<form action="{{ url_for('tables.delete_table', table_id=table.table_id) }}" method="post"
|
||||
class="d-inline">
|
||||
<button type="submit" class="btn btn-danger btn-sm"
|
||||
onclick="return confirm('确认删除该桌台吗?')">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 选择策略模态框 -->
|
||||
<div class="modal fade" id="selectStrategyModal" tabindex="-1" aria-labelledby="selectStrategyModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="selectStrategyModalLabel">选择收款策略</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form onsubmit="selectStrategy(event)">
|
||||
<input type="hidden" id="selectedTableId">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="strategySelect">收款策略</label>
|
||||
<select class="form-control" id="strategySelect" required>
|
||||
<!-- 动态填充策略选项 -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
async function openSelectStrategyModal(tableId) {
|
||||
try {
|
||||
const token = "{{ session['token'] }}";
|
||||
const response = await fetch('/admin/strategies/list', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
const strategies = await response.json();
|
||||
|
||||
const strategySelect = document.getElementById('strategySelect');
|
||||
strategySelect.innerHTML = '';
|
||||
|
||||
strategies.forEach(strategy => {
|
||||
const option = document.createElement('option');
|
||||
option.value = strategy.strategy_id;
|
||||
option.textContent = strategy.strategy_name;
|
||||
strategySelect.appendChild(option);
|
||||
});
|
||||
|
||||
document.getElementById('selectedTableId').value = tableId;
|
||||
const myModal = new bootstrap.Modal(document.getElementById('selectStrategyModal'));
|
||||
myModal.show();
|
||||
} catch (error) {
|
||||
console.error('获取策略列表失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function selectStrategy(event) {
|
||||
event.preventDefault();
|
||||
const tableId = document.getElementById('selectedTableId').value;
|
||||
const strategyId = document.getElementById('strategySelect').value;
|
||||
const token = "{{ session['token'] }}";
|
||||
|
||||
try {
|
||||
const response = await fetch(`/admin/tables/${tableId}/strategy`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
strategy_id: strategyId
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const myModal = bootstrap.Modal.getInstance(document.getElementById('selectStrategyModal'));
|
||||
myModal.hide();
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert(`更新失败: ${error.detail}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新策略失败:', error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user