Ethscriptions commited on
Commit
e522499
·
verified ·
1 Parent(s): 096c697

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +294 -0
app.py ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import streamlit as st
3
+ from datetime import datetime, timedelta
4
+ import matplotlib.pyplot as plt
5
+ import io
6
+ import base64
7
+ import matplotlib.gridspec as gridspec
8
+ import math
9
+ from matplotlib.backends.backend_pdf import PdfPages
10
+ from matplotlib.patches import FancyBboxPatch
11
+
12
+ SPLIT_TIME = "17:30"
13
+ BUSINESS_START = "09:30"
14
+ BUSINESS_END = "01:30"
15
+ BORDER_COLOR = '#A9A9A9'
16
+ DATE_COLOR = '#A9A9A9'
17
+
18
+ def process_schedule(file):
19
+ """处理上传的 Excel 文件,生成排序和分组后的打印内容"""
20
+ try:
21
+ # 读取 Excel,跳过前 8 行
22
+ df = pd.read_excel(file, skiprows=8)
23
+
24
+ # 提取所需列 (G9, H9, J9)
25
+ df = df.iloc[:, [6, 7, 9]] # G, H, J 列
26
+ df.columns = ['Hall', 'StartTime', 'EndTime']
27
+
28
+ # 清理数据
29
+ df = df.dropna(subset=['Hall', 'StartTime', 'EndTime'])
30
+
31
+ # 转换影厅格式为 "#号" 格式
32
+ df['Hall'] = df['Hall'].str.extract(r'(\d+)号').astype(str) + ' '
33
+
34
+ # 保存原始时间字符串用于诊断
35
+ df['original_end'] = df['EndTime']
36
+
37
+ # 转换时间为 datetime 对象
38
+ base_date = datetime.today().date()
39
+ df['StartTime'] = pd.to_datetime(df['StartTime'])
40
+ df['EndTime'] = pd.to_datetime(df['EndTime'])
41
+
42
+ # 设置基准时间
43
+ business_start = datetime.strptime(f"{base_date} {BUSINESS_START}", "%Y-%m-%d %H:%M")
44
+ business_end = datetime.strptime(f"{base_date} {BUSINESS_END}", "%Y-%m-%d %H:%M")
45
+
46
+ # 处理跨天情况
47
+ if business_end < business_start:
48
+ business_end += timedelta(days=1)
49
+
50
+ # 标准化所有时间到同一天
51
+ for idx, row in df.iterrows():
52
+ end_time = row['EndTime']
53
+ if end_time.hour < 9:
54
+ df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
55
+
56
+ if row['StartTime'].hour >= 21 and end_time.hour < 9:
57
+ df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
58
+
59
+ # 筛选营业时间内的场次
60
+ df['time_for_comparison'] = df['EndTime'].apply(
61
+ lambda x: datetime.combine(base_date, x.time())
62
+ )
63
+
64
+ df.loc[df['time_for_comparison'].dt.hour < 9, 'time_for_comparison'] += timedelta(days=1)
65
+
66
+ valid_times = (
67
+ ((df['time_for_comparison'] >= datetime.combine(base_date, business_start.time())) &
68
+ (df['time_for_comparison'] <= datetime.combine(base_date + timedelta(days=1), business_end.time())))
69
+ )
70
+
71
+ df = df[valid_times]
72
+
73
+ # 按散场时间排序
74
+ df = df.sort_values('EndTime')
75
+
76
+ # 分割数据
77
+ split_time = datetime.strptime(f"{base_date} {SPLIT_TIME}", "%Y-%m-%d %H:%M")
78
+ split_time_for_comparison = df['time_for_comparison'].apply(
79
+ lambda x: datetime.combine(base_date, split_time.time())
80
+ )
81
+
82
+ part1 = df[df['time_for_comparison'] <= split_time_for_comparison].copy()
83
+ part2 = df[df['time_for_comparison'] > split_time_for_comparison].copy()
84
+
85
+ # 格式化时间显示
86
+ for part in [part1, part2]:
87
+ part['EndTime'] = part['EndTime'].dt.strftime('%-H:%M')
88
+
89
+ # 关键修改:精确读取C6单元格
90
+ date_df = pd.read_excel(
91
+ file,
92
+ skiprows=5, # 跳过前5行(0-4)
93
+ nrows=1, # 只读1行
94
+ usecols=[2], # 第三列(C列)
95
+ header=None # 无表头
96
+ )
97
+ date_cell = date_df.iloc[0, 0]
98
+
99
+ try:
100
+ # 处理不同日期格式
101
+ if isinstance(date_cell, str):
102
+ date_str = datetime.strptime(date_cell, '%Y-%m-%d').strftime('%Y-%m-%d')
103
+ else:
104
+ date_str = pd.to_datetime(date_cell).strftime('%Y-%m-%d')
105
+ except:
106
+ date_str = datetime.today().strftime('%Y-%m-%d')
107
+
108
+ return part1[['Hall', 'EndTime']], part2[['Hall', 'EndTime']], date_str
109
+
110
+ except Exception as e:
111
+ st.error(f"处理文件时出错: {str(e)}")
112
+ return None, None, None
113
+
114
+ def create_print_layout(data, title, date_str):
115
+ """创建打印布局 (PNG 和 PDF),动态调整字体大小"""
116
+ if data.empty:
117
+ return None
118
+
119
+ # --- 创建 PNG 图形 ---
120
+ png_fig = plt.figure(figsize=(5.83, 8.27), dpi=300) # A5 竖向
121
+ png_ax_container = png_fig.add_subplot(111)
122
+ png_ax_container.set_axis_off()
123
+ png_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
124
+
125
+ # --- 创建 PDF 图形 ---
126
+ pdf_fig = plt.figure(figsize=(5.83, 8.27), dpi=300) # A5 竖向
127
+ pdf_ax_container = pdf_fig.add_subplot(111)
128
+ pdf_ax_container.set_axis_off()
129
+ pdf_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
130
+
131
+ # --- 内部绘图函数 ---
132
+ def process_figure(fig, is_pdf=False):
133
+ plt.rcParams['font.family'] = 'sans-serif'
134
+ plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'Heiti TC', 'SimHei'] # 添加备用中文字体
135
+
136
+ total_items = len(data)
137
+ num_cols = 3
138
+ num_rows = math.ceil(total_items / num_cols)
139
+
140
+ gs = gridspec.GridSpec(num_rows + 1, num_cols, hspace=0.05, wspace=0.05, height_ratios=[0.1] + [1] * num_rows, figure=fig)
141
+
142
+ # --- 新增:预先计算单元格的目标宽度(以像素为单位)---
143
+ target_width_px = 1
144
+ if total_items > 0:
145
+ # 创建一个临时坐标轴来测量其像素宽度
146
+ ax_temp = fig.add_subplot(gs[1, 0])
147
+ # 必须绘制画布才能使用渲染器并获得几何属性
148
+ fig.canvas.draw()
149
+ # 获取像素宽度并计算目标宽度(90%)
150
+ target_width_px = ax_temp.get_window_extent().width * 0.90
151
+ # 移除临时坐标轴
152
+ ax_temp.remove()
153
+ # --- 预计算结束 ---
154
+
155
+ # 此字体大小计算仅用于顶部的日期
156
+ available_height_per_row = (8.27 * 0.9 * (1 / 1.2)) / num_rows if num_rows > 0 else 1
157
+ date_fontsize = min(40, max(10, available_height_per_row * 72 * 0.5))
158
+
159
+ data_values = data.values.tolist()
160
+ while len(data_values) % num_cols != 0:
161
+ data_values.append(['', ''])
162
+ rows_per_col_layout = math.ceil(len(data_values) / num_cols)
163
+
164
+ sorted_data = [['', '']] * len(data_values)
165
+ for i, item in enumerate(data_values):
166
+ if item[0] and item[1]:
167
+ row_in_col = i % rows_per_col_layout
168
+ col_idx = i // rows_per_col_layout
169
+ new_index = row_in_col * num_cols + col_idx
170
+ if new_index < len(sorted_data):
171
+ sorted_data[new_index] = item
172
+
173
+ # 绘制数据单元格
174
+ for idx, (hall, end_time) in enumerate(sorted_data):
175
+ if hall and end_time:
176
+ row_grid = idx // num_cols + 1
177
+ col_grid = idx % num_cols
178
+
179
+ if row_grid < num_rows + 1:
180
+ ax = fig.add_subplot(gs[row_grid, col_grid])
181
+ for spine in ax.spines.values():
182
+ spine.set_visible(False)
183
+
184
+ bbox = FancyBboxPatch(
185
+ (0.01, 0.01), 0.98, 0.98,
186
+ boxstyle="round,pad=0,rounding_size=0.02",
187
+ edgecolor=BORDER_COLOR, facecolor='none',
188
+ linewidth=0.5, transform=ax.transAxes, clip_on=False
189
+ )
190
+ ax.add_patch(bbox)
191
+
192
+ display_text = f"{hall}{end_time}"
193
+
194
+ # --- 修改部分:动态字体大小调整逻辑 ---
195
+ # 创建一个文本对象
196
+ t = ax.text(0.5, 0.5, display_text,
197
+ fontweight='bold', ha='center', va='center',
198
+ transform=ax.transAxes)
199
+
200
+ # 从一个较大的字号开始,迭代查找最佳字号
201
+ current_size = 120 # 从更大的字号开始
202
+ while current_size > 1:
203
+ t.set_fontsize(current_size)
204
+ # 获取渲染后的文本边界框
205
+ text_bbox = t.get_window_extent(renderer=fig.canvas.get_renderer())
206
+
207
+ # 如果文本宽度小于等于目标宽度,则此字号适用
208
+ if text_bbox.width <= target_width_px:
209
+ break
210
+ current_size -= 2 # 步长可以大一点以提高速度
211
+ # --- 修改结束 ---
212
+
213
+ ax.set_xticks([])
214
+ ax.set_yticks([])
215
+ else:
216
+ print(f"Warning: Index out of bounds - idx={idx}, row_grid={row_grid}, col_grid={col_grid}")
217
+
218
+ # 添加日期信息
219
+ ax_date = fig.add_subplot(gs[0, :])
220
+ ax_date.text(0.01, 0.5, f"{date_str} {title}",
221
+ fontsize=date_fontsize * 0.5, # 日期使用之前计算的字号
222
+ color=DATE_COLOR, fontweight='bold',
223
+ ha='left', va='center', transform=ax_date.transAxes)
224
+ for spine in ax_date.spines.values():
225
+ spine.set_visible(False)
226
+ ax_date.set_xticks([])
227
+ ax_date.set_yticks([])
228
+ ax_date.set_facecolor('none')
229
+
230
+ # --- 处理图形 ---
231
+ process_figure(png_fig)
232
+ process_figure(pdf_fig, is_pdf=True)
233
+
234
+ # --- 保存 PNG ---
235
+ png_buffer = io.BytesIO()
236
+ png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.02)
237
+ png_buffer.seek(0)
238
+ png_base64 = base64.b64encode(png_buffer.getvalue()).decode()
239
+ plt.close(png_fig)
240
+
241
+ # --- 保存 PDF ---
242
+ pdf_buffer = io.BytesIO()
243
+ with PdfPages(pdf_buffer) as pdf:
244
+ pdf.savefig(pdf_fig, bbox_inches='tight', pad_inches=0.02)
245
+ pdf_buffer.seek(0)
246
+ pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
247
+ plt.close(pdf_fig)
248
+
249
+ return {
250
+ 'png': f'data:image/png;base64,{png_base64}',
251
+ 'pdf': f'data:application/pdf;base64,{pdf_base64}'
252
+ }
253
+
254
+ def display_pdf(base64_pdf):
255
+ """在Streamlit中嵌入显示PDF"""
256
+ pdf_display = f'<iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>'
257
+ return pdf_display
258
+
259
+ # Streamlit 界面
260
+ st.set_page_config(page_title="散厅时间快捷打印", layout="wide")
261
+ st.title("散厅时间快捷打印")
262
+
263
+ uploaded_file = st.file_uploader("上传【放映场次核对表.xls】文件", type=["xls"])
264
+
265
+ if uploaded_file:
266
+ part1, part2, date_str = process_schedule(uploaded_file)
267
+
268
+ if part1 is not None and part2 is not None:
269
+ part1_output = create_print_layout(part1, "A", date_str)
270
+ part2_output = create_print_layout(part2, "C", date_str)
271
+
272
+ col1, col2 = st.columns(2)
273
+
274
+ with col1:
275
+ st.subheader("白班散场预览(时间 ≤ 17:30)")
276
+ if part1_output:
277
+ tab1_1, tab1_2 = st.tabs(["PDF 预览", "PNG 预览"])
278
+ with tab1_1:
279
+ st.markdown(display_pdf(part1_output['pdf']), unsafe_allow_html=True)
280
+ with tab1_2:
281
+ st.image(part1_output['png'])
282
+ else:
283
+ st.info("白班部分没有数据")
284
+
285
+ with col2:
286
+ st.subheader("夜班散场预览(时间 > 17:30)")
287
+ if part2_output:
288
+ tab2_1, tab2_2 = st.tabs(["PDF 预览", "PNG 预览"])
289
+ with tab2_1:
290
+ st.markdown(display_pdf(part2_output['pdf']), unsafe_allow_html=True)
291
+ with tab2_2:
292
+ st.image(part2_output['png'])
293
+ else:
294
+ st.info("夜班部分没有数据")