end-print / app.py
Ethscriptions's picture
Update app.py
c1f32de verified
raw
history blame
8.24 kB
import pandas as pd
import streamlit as st
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import io
import base64
import matplotlib.gridspec as gridspec
import math
# [前面的常量定义和 process_schedule 函数保持不变]
SPLIT_TIME = "17:30"
BUSINESS_START = "09:30"
BUSINESS_END = "01:30"
BORDER_COLOR = '#A9A9A9'
def process_schedule(file):
"""处理上传的 Excel 文件,生成排序和分组后的打印内容"""
try:
# 读取 Excel,跳过前 8 行
df = pd.read_excel(file, skiprows=8)
# 提取所需列 (G9, H9, J9)
df = df.iloc[:, [6, 7, 9]] # G, H, J 列
df.columns = ['Hall', 'StartTime', 'EndTime']
# 清理数据
df = df.dropna(subset=['Hall', 'StartTime', 'EndTime'])
# 转换影厅格式为 "#号" 格式
df['Hall'] = df['Hall'].str.extract(r'(\d+)号').astype(str) + ' '
# 保存原始时间字符串用于诊断
df['original_end'] = df['EndTime']
# 转换时间为 datetime 对象
base_date = datetime.today().date()
df['StartTime'] = pd.to_datetime(df['StartTime'])
df['EndTime'] = pd.to_datetime(df['EndTime'])
# 设置基准时间
business_start = datetime.strptime(f"{base_date} {BUSINESS_START}", "%Y-%m-%d %H:%M")
business_end = datetime.strptime(f"{base_date} {BUSINESS_END}", "%Y-%m-%d %H:%M")
# 处理跨天情况
if business_end < business_start:
business_end += timedelta(days=1)
# 标准化所有时间到同一天
for idx, row in df.iterrows():
end_time = row['EndTime']
# 如果结束时间在凌晨(0点到9:30之间),认为是第二天
if end_time.hour < 9:
df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
# 如果开始时间在晚上9点之后,结束时间在凌晨,也需要调整结束时间
if row['StartTime'].hour >= 21 and end_time.hour < 9:
df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
# 筛选营业时间内的场次
# 转换时间为当天的时间点进行比较
df['time_for_comparison'] = df['EndTime'].apply(
lambda x: datetime.combine(base_date, x.time())
)
# 处理跨天的情况
df.loc[df['time_for_comparison'].dt.hour < 9, 'time_for_comparison'] += timedelta(days=1)
# 应用时间筛选
valid_times = (
((df['time_for_comparison'] >= datetime.combine(base_date, business_start.time())) &
(df['time_for_comparison'] <= datetime.combine(base_date + timedelta(days=1), business_end.time())))
)
df = df[valid_times]
# 按散场时间排序
df = df.sort_values('EndTime')
# 分割数据
split_time = datetime.strptime(f"{base_date} {SPLIT_TIME}", "%Y-%m-%d %H:%M")
split_time_for_comparison = df['time_for_comparison'].apply(
lambda x: datetime.combine(base_date, split_time.time())
)
part1 = df[df['time_for_comparison'] <= split_time_for_comparison].copy()
part2 = df[df['time_for_comparison'] > split_time_for_comparison].copy()
# 格式化时间显示
for part in [part1, part2]:
part['EndTime'] = part['EndTime'].dt.strftime('%-I:%M')
# 提取日期
date_df = pd.read_excel(file, skiprows=5, nrows=1, usecols="C")
date_str = date_df.iloc[0, 0].strftime('%Y-%m-%d') if not pd.isna(date_df.iloc[0, 0]) else datetime.today().strftime('%Y-%m-%d')
return part1[['Hall', 'EndTime']], part2[['Hall', 'EndTime']], date_str
except Exception as e:
st.error(f"处理文件时出错: {str(e)}")
return None, None, None
def create_print_layout(data, title, date_str):
"""创建打印布局"""
if data.empty:
return None
# 设置 A5 纸张竖向尺寸
fig = plt.figure(figsize=(5.83, 8.27), dpi=300)
plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.1)
# 设置字体
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['SimHei']
# 计算行数和总数
total_items = len(data)
num_cols = 3
num_rows = math.ceil(total_items / num_cols)
# 创建网格
gs = gridspec.GridSpec(num_rows + 1, num_cols, hspace=0.1, wspace=0.1, height_ratios=[1] * num_rows + [0.2])
# 计算字体大小
base_fontsize = min(45, 265 / num_rows)
# 重要改动:正确的竖向排序逻辑
data_values = data.values.tolist()
# 确保数据长度是3的倍数
while len(data_values) % 3 != 0:
data_values.append(['', ''])
# 计算每列应该包含的行数
rows_per_col = math.ceil(len(data_values) / 3)
# 创建一个新的数据列表,用于存储重新排序后的数据
sorted_data = [['', '']] * len(data_values)
# 正确的竖向排序逻辑
for i, item in enumerate(data_values):
if item[0] and item[1]: # 只处理非空数据
# 计算新的位置
row = i % rows_per_col
col = i // rows_per_col
new_index = row * 3 + col
if new_index < len(sorted_data):
sorted_data[new_index] = item
# 填充数据
for idx, (hall, end_time) in enumerate(sorted_data):
if hall and end_time: # 只处理非空数据
row = idx // 3
col = idx % 3
ax = plt.subplot(gs[row, col])
# 设置边框
for spine in ax.spines.values():
spine.set_color(BORDER_COLOR)
spine.set_linewidth(0.5)
# 显示文本
display_text = f"{hall}{end_time}"
ax.text(0.5, 0.5, display_text,
fontsize=base_fontsize,
fontweight='bold',
ha='center',
va='center')
# 设置边距
ax.set_xlim(-0.02, 1.02)
ax.set_ylim(-0.02, 1.02)
# 移除坐标轴
ax.set_xticks([])
ax.set_yticks([])
# 在底部添加日期和班次信息
ax_bottom = plt.subplot(gs[num_rows, :])
ax_bottom.text(0.5, 0.5, f"{date_str} {title}",
fontsize=base_fontsize * 0.8,
fontweight='bold',
ha='center',
va='center')
# 移除底部坐标轴
ax_bottom.set_xticks([])
ax_bottom.set_yticks([])
for spine in ax_bottom.spines.values():
spine.set_visible(False)
# 转换为图片
buffer = io.BytesIO()
plt.savefig(buffer, format='png', bbox_inches='tight', pad_inches=0.05)
buffer.seek(0)
image_base64 = base64.b64encode(buffer.getvalue()).decode()
plt.close()
return f'data:image/png;base64,{image_base64}'
# Streamlit 界面
st.set_page_config(page_title="散厅时间快捷打印", layout="wide")
st.title("散厅时间快捷打印")
uploaded_file = st.file_uploader("上传【放映场次核对表.xls】文件", type=["xls", "xlsx"])
if uploaded_file:
part1, part2, date_str = process_schedule(uploaded_file)
if part1 is not None and part2 is not None:
# 创建打印布局
part1_image = create_print_layout(part1, "白班", date_str)
part2_image = create_print_layout(part2, "夜班", date_str)
# 显示预览
col1, col2 = st.columns(2)
with col1:
st.subheader("白班散场预览(时间 ≤ 17:30)")
if part1_image:
st.image(part1_image)
else:
st.info("白班部分没有数据")
with col2:
st.subheader("夜班散场预览(时间 > 17:30)")
if part2_image:
st.image(part2_image)
else:
st.info("夜班部分没有数据")