end-print / app.py
Ethscriptions's picture
Create app.py
c8dda34 verified
raw
history blame
6.8 kB
import pandas as pd
import streamlit as st
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import io
import base64
from matplotlib.patches import Rectangle
import matplotlib.gridspec as gridspec
# 定义全局常量
SPLIT_TIME = "17:30" # 分界线时间
BUSINESS_START = "09:30" # 营业开始时间
BUSINESS_END = "01:30" # 营业结束时间
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['StartTime'] = pd.to_datetime(df['StartTime']).dt.strftime('%H:%M')
df['EndTime'] = pd.to_datetime(df['EndTime']).dt.strftime('%H:%M')
# 将时间转换为 datetime 对象便于比较
base_date = datetime.today().date()
df['StartTime'] = pd.to_datetime(base_date.strftime('%Y-%m-%d ') + df['StartTime'])
df['EndTime'] = pd.to_datetime(base_date.strftime('%Y-%m-%d ') + df['EndTime'])
# 处理跨天情况
df.loc[df['EndTime'] < df['StartTime'], 'EndTime'] += timedelta(days=1)
# 筛选营业时间内的场次
business_start = datetime.strptime(base_date.strftime('%Y-%m-%d ') + BUSINESS_START, '%Y-%m-%d %H:%M')
business_end = datetime.strptime(base_date.strftime('%Y-%m-%d ') + BUSINESS_END, '%Y-%m-%d %H:%M')
if business_end < business_start:
business_end += timedelta(days=1)
mask = (
(df['StartTime'] >= business_start) |
(df['StartTime'] <= business_end)
)
df = df[mask]
# 按散场时间排序
df = df.sort_values('EndTime')
# 分割数据
split_time = datetime.strptime(base_date.strftime('%Y-%m-%d ') + SPLIT_TIME, '%Y-%m-%d %H:%M')
part1 = df[df['EndTime'] <= split_time].copy()
part2 = df[df['EndTime'] > split_time].copy()
# 格式化时间显示
for part in [part1, part2]:
part['EndTime'] = part['EndTime'].dt.strftime('%-I:%M')
return part1[['Hall', 'EndTime']], part2[['Hall', 'EndTime']]
except Exception as e:
st.error(f"处理文件时出错: {str(e)}")
return None, None
def create_print_layout(data, title):
"""创建打印布局"""
if data.empty:
return None
# 设置 A5 横向尺寸(单位:英寸)
fig = plt.figure(figsize=(8.27, 5.83), dpi=300)
# 减小边距,最大化利用空间
plt.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
# 创建网格,减小间距
rows = (len(data) + 3) // 4 # 向上取整除法
gs = gridspec.GridSpec(rows, 4, hspace=0.1, wspace=0.1) # 显著减小间距
# 设置字体和样式
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文字体
# 计算适合的字体大小
base_fontsize = min(32, 180 / rows) # 根据行数动态调整基础字体大小
# 填充数据
for idx, (hall, end_time) in enumerate(data.values):
row = idx // 4
col = idx % 4
# 创建子图
ax = plt.subplot(gs[row, col])
# 设置边框样式
for spine in ax.spines.values():
spine.set_color('lightgray')
spine.set_linewidth(0.5)
# 调整文本位置,增加上下间距
# 将影厅号向上移动,时间向下移动
ax.text(0.5, 0.65, hall, # y 位置从 0.55 改为 0.65
fontsize=base_fontsize,
fontweight='bold',
ha='center',
va='center')
ax.text(0.5, 0.2, end_time, # y 位置从 0.25 改为 0.2
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([])
# 将图表转换为 base64 字符串
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}'
def get_html_content(image_base64):
"""生成打印页面的 HTML 内容"""
return f"""
<!DOCTYPE html>
<html>
<head>
<style>
@page {{
size: A5 landscape;
margin: 0;
}}
body {{
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}}
img {{
width: 100%;
height: 100%;
object-fit: fill;
}}
</style>
<script>
window.onload = function() {{
window.print();
}}
</script>
</head>
<body>
<img src="{image_base64}">
</body>
</html>
"""
# 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 = process_schedule(uploaded_file)
if part1 is not None and part2 is not None:
# 创建打印布局
part1_image = create_print_layout(part1, "第一部分")
part2_image = create_print_layout(part2, "第二部分")
# 显示预览
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("第二部分没有数据")