Ethscriptions commited on
Commit
95f254d
·
verified ·
1 Parent(s): 34b21e7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -162
app.py CHANGED
@@ -4,246 +4,237 @@ 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
 
11
- # 常量定义
12
  SPLIT_TIME = "17:30"
13
  BUSINESS_START = "09:30"
14
  BUSINESS_END = "01:30"
15
- BORDER_COLOR = 'grey' # 边框颜色更新为灰色
16
  DATE_COLOR = '#A9A9A9'
17
- A5_WIDTH_IN = 5.83 # A5 宽度 (英寸)
18
- A5_HEIGHT_IN = 8.27 # A5 高度 (英寸)
 
19
 
20
  def process_schedule(file):
21
  """处理上传的 Excel 文件,生成排序和分组后的打印内容"""
22
  try:
23
  # 读取 Excel,跳过前 8 行
24
  df = pd.read_excel(file, skiprows=8)
25
-
26
  # 提取所需列 (G9, H9, J9)
27
  df = df.iloc[:, [6, 7, 9]] # G, H, J 列
28
  df.columns = ['Hall', 'StartTime', 'EndTime']
29
-
30
  # 清理数据
31
  df = df.dropna(subset=['Hall', 'StartTime', 'EndTime'])
32
-
33
  # 转换影厅格式为 "#号" 格式
34
- df['Hall'] = df['Hall'].str.extract(r'(\d+)').astype(str) + '号厅 '
35
-
36
  # 保存原始时间字符串用于诊断
37
  df['original_end'] = df['EndTime']
38
-
39
  # 转换时间为 datetime 对象
40
  base_date = datetime.today().date()
41
  df['StartTime'] = pd.to_datetime(df['StartTime'])
42
  df['EndTime'] = pd.to_datetime(df['EndTime'])
43
-
44
  # 设置基准时间
45
  business_start = datetime.strptime(f"{base_date} {BUSINESS_START}", "%Y-%m-%d %H:%M")
46
  business_end = datetime.strptime(f"{base_date} {BUSINESS_END}", "%Y-%m-%d %H:%M")
47
-
48
  # 处理跨天情况
49
  if business_end < business_start:
50
  business_end += timedelta(days=1)
51
-
52
  # 标准化所有时间到同一天
53
  for idx, row in df.iterrows():
54
  end_time = row['EndTime']
55
- # 如果结束时间在凌晨0点到9点之间,则认为是第二天的场次
56
  if end_time.hour < 9:
57
  df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
58
- # 如果开始时间在21点后,结束时间在9点前,也认为是跨天场次
59
- elif row['StartTime'].hour >= 21 and end_time.hour < 9:
60
  df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
61
 
62
- # 为了正确筛选和排序,创建一个统一比较的时间列
63
- # 该列将所有时间都映射到正确的日期上
64
  df['time_for_comparison'] = df['EndTime'].apply(
65
- lambda x: x if x.hour >= 9 else x + timedelta(days=1) if x.date() == base_date else x
 
 
 
 
 
 
 
66
  )
67
 
68
- # 筛选营业时间内的场次
69
- valid_times = (df['time_for_comparison'] >= business_start) & (df['time_for_comparison'] <= business_end)
70
  df = df[valid_times]
71
-
72
  # 按散场时间排序
73
- df = df.sort_values('time_for_comparison')
74
-
75
  # 分割数据
76
  split_time = datetime.strptime(f"{base_date} {SPLIT_TIME}", "%Y-%m-%d %H:%M")
77
-
78
- part1 = df[df['time_for_comparison'] <= split_time].copy()
79
- part2 = df[df['time_for_comparison'] > split_time].copy()
80
-
 
 
 
81
  # 格式化时间显示
82
  for part in [part1, part2]:
83
- part['EndTime'] = part['EndTime'].dt.strftime('%-H:%M')
84
-
85
- # 精确读取C6单元格的日期
86
  date_df = pd.read_excel(
87
  file,
88
- skiprows=5,
89
- nrows=1,
90
- usecols=[2],
91
- header=None
92
  )
93
  date_cell = date_df.iloc[0, 0]
94
-
95
  try:
 
96
  if isinstance(date_cell, str):
97
  date_str = datetime.strptime(date_cell, '%Y-%m-%d').strftime('%Y-%m-%d')
98
  else:
99
  date_str = pd.to_datetime(date_cell).strftime('%Y-%m-%d')
100
  except:
101
  date_str = datetime.today().strftime('%Y-%m-%d')
102
-
103
  return part1[['Hall', 'EndTime']], part2[['Hall', 'EndTime']], date_str
104
-
105
  except Exception as e:
106
  st.error(f"处理文件时出错: {str(e)}")
107
  return None, None, None
108
 
 
109
  def create_print_layout(data, title, date_str):
110
- """创建符合新布局要求的打印视图 (PNG 和 PDF)"""
111
  if data.empty:
112
  return None
113
 
114
- # --- 布局计算 (要求1) ---
 
 
 
 
 
 
 
 
115
  total_items = len(data)
116
  num_cols = 3
117
- # 计算行数,小数位进一
118
- num_rows = math.ceil(total_items / num_cols)
119
- if num_rows == 0: # 避免除以零
120
- return None
121
 
122
- # 计算留空距离
123
- # A5高度除以行数,再除以4,得到上下左右留空
124
- margin_y = (A5_HEIGHT_IN / num_rows) / 4
125
- margin_x = margin_y # 保持长宽比
126
-
127
- # 计算绘图区域在figure中的相对位置
128
- left_margin = margin_x / A5_WIDTH_IN
129
- right_margin = 1 - (margin_x / A5_WIDTH_IN)
130
- bottom_margin = margin_y / A5_HEIGHT_IN
131
- top_margin = 1 - (margin_y / A5_HEIGHT_IN)
132
-
133
- # --- 创建图形 ---
134
- # 使用相同的 figure 和函数来确保 PNG 和 PDF 的一致性
135
- fig = plt.figure(figsize=(A5_WIDTH_IN, A5_HEIGHT_IN), dpi=300)
136
-
137
- # --- 内部绘图函数 ---
138
- def process_figure(figure):
139
- plt.rcParams['font.family'] = 'sans-serif'
140
- plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'Heiti TC', 'SimHei'] # 添加备用中文字体
141
-
142
- # 创建网格布局
143
- gs = gridspec.GridSpec(
144
- num_rows + 1, num_cols,
145
- figure=figure,
146
- left=left_margin, right=right_margin,
147
- bottom=bottom_margin, top=top_margin,
148
- hspace=0, wspace=0, # 单元格之间无间距,边框会自己形成网格
149
- height_ratios=[0.2] + [1] * num_rows # 日期行高度较小
150
- )
151
 
152
- # 动态计算字体大小
153
- cell_height_in = (A5_HEIGHT_IN - 2 * margin_y) / num_rows
154
- # 字体大小与单元格高度关联,0.35为经验系数,可以微调
155
- base_fontsize = cell_height_in * 72 * 0.35
156
-
157
- data_values = data.values.tolist()
158
-
159
- # 补全空位,以对齐网格
160
- while len(data_values) % num_cols != 0:
161
- data_values.append(['', ''])
162
-
163
- rows_per_col_layout = math.ceil(len(data_values) / num_cols)
164
-
165
- # 按列优先(Z字形)排序数据
166
- sorted_data = [['', '']] * len(data_values)
167
- item_count = 0
168
- for i, item in enumerate(data_values):
169
- if item[0] and item[1]:
170
- row_in_col = i % rows_per_col_layout
171
- col_idx = i // rows_per_col_layout
172
- new_index = row_in_col * num_cols + col_idx
173
- if new_index < len(sorted_data):
174
- sorted_data[new_index] = item
175
- item_count += 1
176
-
177
- # 重新整理数据,移除空位,方便添加序号
178
- final_data = [item for item in sorted_data if item[0] and item[1]]
179
-
180
- # 绘制数据单元格
181
- for idx, (hall, end_time) in enumerate(final_data):
182
- row_grid = idx // num_cols + 1
183
  col_grid = idx % num_cols
 
 
 
 
 
184
 
185
- ax = figure.add_subplot(gs[row_grid, col_grid])
186
-
187
- # --- 修改开始:绘制单元格 (要求2) ---
188
- # 设置所有边框为灰色点状虚线
189
- for spine in ax.spines.values():
190
- spine.set_visible(True)
191
- spine.set_linestyle((0, (1, 2))) # 点状线样式
192
- spine.set_edgecolor(BORDER_COLOR)
193
- spine.set_linewidth(1)
194
 
195
- # --- 内容与序号 (要求3 & 4) ---
196
- # 主内容
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  display_text = f"{hall}{end_time}"
198
- # 使用 text 函数并设置 bbox 来控制内容宽度为90%
199
  ax.text(0.5, 0.5, display_text,
200
  fontsize=base_fontsize,
201
  fontweight='bold',
202
- ha='center',
203
- va='center',
204
- transform=ax.transAxes,
205
- bbox=dict(boxstyle='square,pad=0', fc='none', ec='none',
206
- width=0.9, height=0.9) # 隐形 bbox 控制宽度
207
- )
208
-
209
- # 左上角序号
210
- ax.text(0.08, 0.92, str(idx + 1),
211
- fontsize=base_fontsize * 0.4, # 序号字体较小
212
- color=DATE_COLOR,
213
- fontweight='normal',
214
- ha='left',
215
- va='top',
216
  transform=ax.transAxes)
217
-
218
- ax.set_xticks([])
219
- ax.set_yticks([])
220
-
221
- # 添加日期和标题
222
- ax_date = figure.add_subplot(gs[0, :])
223
- ax_date.text(0.01, 0.5, f"{date_str} {title}",
224
- fontsize=base_fontsize * 0.6,
225
- color=DATE_COLOR,
226
- fontweight='bold',
227
- ha='left',
228
- va='center',
229
- transform=ax_date.transAxes)
230
- ax_date.set_axis_off()
231
-
232
- # --- 处理和保存图形 ---
233
- process_figure(fig)
234
 
 
235
  # 保存为 PNG
236
  png_buffer = io.BytesIO()
237
- fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0)
238
  png_buffer.seek(0)
239
  png_base64 = base64.b64encode(png_buffer.getvalue()).decode()
240
 
241
  # 保存为 PDF
242
  pdf_buffer = io.BytesIO()
243
- fig.savefig(pdf_buffer, format='pdf', bbox_inches='tight', pad_inches=0)
 
244
  pdf_buffer.seek(0)
245
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
246
-
247
  plt.close(fig)
248
 
249
  return {
@@ -253,43 +244,43 @@ def create_print_layout(data, title, date_str):
253
 
254
  def display_pdf(base64_pdf):
255
  """在Streamlit中嵌入显示PDF"""
256
- pdf_display = f'<iframe src="data:application/pdf;base64,{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", "xlsx"])
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
- with st.spinner('正在生成预览图...'):
270
- part1_output = create_print_layout(part1, "白班", date_str)
271
- part2_output = create_print_layout(part2, "晚班", date_str)
272
 
273
  col1, col2 = st.columns(2)
274
 
275
  with col1:
276
- st.subheader(f"白班散场预览 ({SPLIT_TIME})")
277
  if part1_output:
278
  tab1_1, tab1_2 = st.tabs(["PDF 预览", "PNG 预览"])
279
  with tab1_1:
280
  st.markdown(display_pdf(part1_output['pdf']), unsafe_allow_html=True)
281
  with tab1_2:
282
- st.image(f"data:image/png;base64,{part1_output['png']}")
283
  else:
284
  st.info("白班部分没有数据")
285
 
286
  with col2:
287
- st.subheader(f"晚班散场预览 (>{SPLIT_TIME})")
288
  if part2_output:
289
  tab2_1, tab2_2 = st.tabs(["PDF 预览", "PNG 预览"])
290
  with tab2_1:
291
  st.markdown(display_pdf(part2_output['pdf']), unsafe_allow_html=True)
292
  with tab2_2:
293
- st.image(f"data:image/png;base64,{part2_output['png']}")
294
  else:
295
- st.info("晚班部分没有数据")
 
4
  import matplotlib.pyplot as plt
5
  import io
6
  import base64
 
7
  import math
8
  from matplotlib.backends.backend_pdf import PdfPages
9
+ from matplotlib.patches import Rectangle # Replaced FancyBboxPatch
10
 
11
+ # --- Constants ---
12
  SPLIT_TIME = "17:30"
13
  BUSINESS_START = "09:30"
14
  BUSINESS_END = "01:30"
15
+ BORDER_COLOR = '#CCCCCC' # Light gray for dotted border
16
  DATE_COLOR = '#A9A9A9'
17
+ A5_WIDTH_INCH = 5.83
18
+ A5_HEIGHT_INCH = 8.27
19
+
20
 
21
  def process_schedule(file):
22
  """处理上传的 Excel 文件,生成排序和分组后的打印内容"""
23
  try:
24
  # 读取 Excel,跳过前 8 行
25
  df = pd.read_excel(file, skiprows=8)
26
+
27
  # 提取所需列 (G9, H9, J9)
28
  df = df.iloc[:, [6, 7, 9]] # G, H, J 列
29
  df.columns = ['Hall', 'StartTime', 'EndTime']
30
+
31
  # 清理数据
32
  df = df.dropna(subset=['Hall', 'StartTime', 'EndTime'])
33
+
34
  # 转换影厅格式为 "#号" 格式
35
+ df['Hall'] = df['Hall'].str.extract(r'(\d+)').astype(str) + ' '
36
+
37
  # 保存原始时间字符串用于诊断
38
  df['original_end'] = df['EndTime']
39
+
40
  # 转换时间为 datetime 对象
41
  base_date = datetime.today().date()
42
  df['StartTime'] = pd.to_datetime(df['StartTime'])
43
  df['EndTime'] = pd.to_datetime(df['EndTime'])
44
+
45
  # 设置基准时间
46
  business_start = datetime.strptime(f"{base_date} {BUSINESS_START}", "%Y-%m-%d %H:%M")
47
  business_end = datetime.strptime(f"{base_date} {BUSINESS_END}", "%Y-%m-%d %H:%M")
48
+
49
  # 处理跨天情况
50
  if business_end < business_start:
51
  business_end += timedelta(days=1)
52
+
53
  # 标准化所有时间到同一天
54
  for idx, row in df.iterrows():
55
  end_time = row['EndTime']
 
56
  if end_time.hour < 9:
57
  df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
58
+
59
+ if row['StartTime'].hour >= 21 and end_time.hour < 9:
60
  df.at[idx, 'EndTime'] = end_time + timedelta(days=1)
61
 
62
+ # 筛选营业时间内的场次
 
63
  df['time_for_comparison'] = df['EndTime'].apply(
64
+ lambda x: datetime.combine(base_date, x.time())
65
+ )
66
+
67
+ df.loc[df['time_for_comparison'].dt.hour < 9, 'time_for_comparison'] += timedelta(days=1)
68
+
69
+ valid_times = (
70
+ ((df['time_for_comparison'] >= datetime.combine(base_date, business_start.time())) &
71
+ (df['time_for_comparison'] <= datetime.combine(base_date + timedelta(days=1), business_end.time())))
72
  )
73
 
 
 
74
  df = df[valid_times]
75
+
76
  # 按散场时间排序
77
+ df = df.sort_values('EndTime')
78
+
79
  # 分割数据
80
  split_time = datetime.strptime(f"{base_date} {SPLIT_TIME}", "%Y-%m-%d %H:%M")
81
+ split_time_for_comparison = df['time_for_comparison'].apply(
82
+ lambda x: datetime.combine(base_date, split_time.time())
83
+ )
84
+
85
+ part1 = df[df['time_for_comparison'] <= split_time_for_comparison].copy()
86
+ part2 = df[df['time_for_comparison'] > split_time_for_comparison].copy()
87
+
88
  # 格式化时间显示
89
  for part in [part1, part2]:
90
+ part['EndTime'] = part['EndTime'].dt.strftime('%-I:%M')
91
+
92
+ # 精确读取C6单元格
93
  date_df = pd.read_excel(
94
  file,
95
+ skiprows=5, # 跳过前5行(0-4)
96
+ nrows=1, # 只读1行
97
+ usecols=[2], # 第三列(C列)
98
+ header=None # 无表头
99
  )
100
  date_cell = date_df.iloc[0, 0]
101
+
102
  try:
103
+ # 处理不同日期格式
104
  if isinstance(date_cell, str):
105
  date_str = datetime.strptime(date_cell, '%Y-%m-%d').strftime('%Y-%m-%d')
106
  else:
107
  date_str = pd.to_datetime(date_cell).strftime('%Y-%m-%d')
108
  except:
109
  date_str = datetime.today().strftime('%Y-%m-%d')
110
+
111
  return part1[['Hall', 'EndTime']], part2[['Hall', 'EndTime']], date_str
112
+
113
  except Exception as e:
114
  st.error(f"处理文件时出错: {str(e)}")
115
  return None, None, None
116
 
117
+
118
  def create_print_layout(data, title, date_str):
119
+ """创建符合新设计要求的打印布局 (PNG 和 PDF)"""
120
  if data.empty:
121
  return None
122
 
123
+ # --- 1. 初始化 Figure ---
124
+ fig = plt.figure(figsize=(A5_WIDTH_INCH, A5_HEIGHT_INCH), dpi=300)
125
+ # 使用整个figure区域,手动控制所有边距
126
+ fig.subplots_adjust(left=0, right=1, top=1, bottom=0)
127
+
128
+ plt.rcParams['font.family'] = 'sans-serif'
129
+ plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
130
+
131
+ # --- 2. 数据准备和布局计算 ---
132
  total_items = len(data)
133
  num_cols = 3
134
+ data_rows = math.ceil(total_items / num_cols)
135
+ if data_rows == 0: data_rows = 1 # 避免除以零
 
 
136
 
137
+ # 为header分配固定高度 (占总高度的8%), 剩余为数据区
138
+ header_height_norm = 0.08
139
+ data_area_height_norm = 1.0 - header_height_norm
140
+
141
+ # 每个数据单元格的尺寸 (标准化坐标, 0-1)
142
+ cell_height_norm = data_area_height_norm / data_rows
143
+ cell_width_norm = 1.0 / num_cols
144
+
145
+ # 根据单元格高度计算留白距离 (高度的 1/4 作为边距)
146
+ # 这将被用作单元格内部的padding
147
+ spacing_norm_y = cell_height_norm / 4
148
+ spacing_norm_x = cell_width_norm / 4 # 对称的边距
149
+
150
+ # 准备数据 (Z字形排序)
151
+ data_values = data.values.tolist()
152
+ while len(data_values) % num_cols != 0:
153
+ data_values.append(['', ''])
154
+
155
+ rows_per_col_layout = math.ceil(len(data_values) / num_cols)
156
+ sorted_data = [['', '']] * len(data_values)
157
+ for i, item in enumerate(data_values):
158
+ if item[0] and item[1]:
159
+ row_in_col = i % rows_per_col_layout
160
+ col_idx = i // rows_per_col_layout
161
+ new_index = row_in_col * num_cols + col_idx
162
+ if new_index < len(sorted_data):
163
+ sorted_data[new_index] = item
 
 
164
 
165
+ # --- 3. 绘制元素 ---
166
+
167
+ # 绘制页眉 (日期和标题)
168
+ ax_date = fig.add_axes([0, data_area_height_norm, 1, header_height_norm])
169
+ ax_date.text(0.02, 0.5, f"{date_str} {title}",
170
+ fontsize=12, color=DATE_COLOR, fontweight='bold',
171
+ ha='left', va='center', transform=ax_date.transAxes)
172
+ ax_date.set_axis_off()
173
+
174
+ # 动态计算主字体大小,使其适应单元格高度
175
+ # 1英寸 = 72点。字体大小占单元格高度的40%左右
176
+ base_fontsize = (cell_height_norm * A5_HEIGHT_INCH) * 72 * 0.4
177
+
178
+ # 绘制数据单元格
179
+ for idx, (hall, end_time) in enumerate(sorted_data):
180
+ if hall and end_time:
181
+ row_grid = idx // num_cols
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  col_grid = idx % num_cols
183
+
184
+ # 计算单元格(Axes)的位置和尺寸
185
+ ax_left = col_grid * cell_width_norm
186
+ # Y坐标从下往上,所以要从数据区顶部开始往下减
187
+ ax_bottom = data_area_height_norm - (row_grid + 1) * cell_height_norm
188
 
189
+ ax = fig.add_axes([ax_left, ax_bottom, cell_width_norm, cell_height_norm])
190
+ ax.set_axis_off()
191
+
192
+ # 要求 2 & 3: 创建点状虚线边框和呼吸感
193
+ # 通过在Axes内部绘制一个稍小的矩形来实现
194
+ border_padding_x = spacing_norm_x / 2
195
+ border_padding_y = spacing_norm_y / 2
 
 
196
 
197
+ border_rect = Rectangle(
198
+ (border_padding_x, border_padding_y), # 左下角坐标 (x,y)
199
+ 1 - 2 * border_padding_x, # 宽度
200
+ 1 - 2 * border_padding_y, # 高度
201
+ transform=ax.transAxes,
202
+ edgecolor=BORDER_COLOR,
203
+ facecolor='none',
204
+ linestyle=':', # 点状虚线
205
+ linewidth=2 # 让点更明显
206
+ )
207
+ ax.add_patch(border_rect)
208
+
209
+ # 要求 4: 在左上角添加序号
210
+ ax.text(border_padding_x + 0.02, 1 - border_padding_y - 0.02, str(idx + 1),
211
+ fontsize=base_fontsize * 0.3, # 序号字体较小
212
+ color='grey',
213
+ ha='left', va='top',
214
+ transform=ax.transAxes)
215
+
216
+ # 要求 3: 居中显示主要文本
217
  display_text = f"{hall}{end_time}"
 
218
  ax.text(0.5, 0.5, display_text,
219
  fontsize=base_fontsize,
220
  fontweight='bold',
221
+ ha='center', va='center',
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  transform=ax.transAxes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
 
224
+ # --- 4. 保存到内存 ---
225
  # 保存为 PNG
226
  png_buffer = io.BytesIO()
227
+ fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.02)
228
  png_buffer.seek(0)
229
  png_base64 = base64.b64encode(png_buffer.getvalue()).decode()
230
 
231
  # 保存为 PDF
232
  pdf_buffer = io.BytesIO()
233
+ with PdfPages(pdf_buffer) as pdf:
234
+ pdf.savefig(fig, bbox_inches='tight', pad_inches=0.02)
235
  pdf_buffer.seek(0)
236
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()
237
+
238
  plt.close(fig)
239
 
240
  return {
 
244
 
245
  def display_pdf(base64_pdf):
246
  """在Streamlit中嵌入显示PDF"""
247
+ pdf_display = f'<iframe src="{base64_pdf}" width="100%" height="800" type="application/pdf"></iframe>'
248
  return pdf_display
249
 
250
+ # --- Streamlit 界面 (无变化) ---
251
  st.set_page_config(page_title="散厅时间快捷打印", layout="wide")
252
  st.title("散厅时间快捷打印")
253
 
254
+ uploaded_file = st.file_uploader("上传【放映场次核对表.xls】文件", type=["xls"])
255
 
256
  if uploaded_file:
257
  part1, part2, date_str = process_schedule(uploaded_file)
258
 
259
  if part1 is not None and part2 is not None:
260
+ # 生成包含 PNG 和 PDF 的字典
261
+ part1_output = create_print_layout(part1, "A", date_str)
262
+ part2_output = create_print_layout(part2, "C", date_str)
263
 
264
  col1, col2 = st.columns(2)
265
 
266
  with col1:
267
+ st.subheader("白班散场预览(时间 17:30)")
268
  if part1_output:
269
  tab1_1, tab1_2 = st.tabs(["PDF 预览", "PNG 预览"])
270
  with tab1_1:
271
  st.markdown(display_pdf(part1_output['pdf']), unsafe_allow_html=True)
272
  with tab1_2:
273
+ st.image(part1_output['png'])
274
  else:
275
  st.info("白班部分没有数据")
276
 
277
  with col2:
278
+ st.subheader("夜班散场预览(时间 > 17:30)")
279
  if part2_output:
280
  tab2_1, tab2_2 = st.tabs(["PDF 预览", "PNG 预览"])
281
  with tab2_1:
282
  st.markdown(display_pdf(part2_output['pdf']), unsafe_allow_html=True)
283
  with tab2_2:
284
+ st.image(part2_output['png'])
285
  else:
286
+ st.info("夜班部分没有数据")