自制美食
持续更新......
蒜蓉粉丝虾

京酱肉丝

小白菜烧豆腐

上海炸猪排


酱爆肉丝

临沂炒鸡

水煮肉片

青椒牛肉

香菜牛肉

照烧鸡翅根

芥末虾球

烤五花肉·烤箱版


上海红烧肉;

手工煎饺;

香煎罗非鱼;

重庆辣子鸡;

东北地三鲜;

剁椒大鱼头;

西安油泼面

自制薯条.空气炸锅版

青花椒烤鱼

雪菜黄鱼面

蒸咸肉

高汤米苋

口水虾

凉拌茄子

回锅肉

蟹黄豆腐

香葱跑蛋
































假设我的半年总销量是500件货物,每次找厂家订货大约需要80元订货手续费用(包括电话、出差、合同等),仓库费用0.4元/件/月,假设我的日销售数量是平均的,如果设单次进货量为X的话,总费用包括订货手续费用和仓储费用,总费用的变化趋势是怎样的?X的值多少最合适?
首先统一时间、成本单位,避免计算偏差:
| 核心参数 | 具体数值与说明 |
|---|---|
| 半年总需求量(D) | 500件(日销量平均,即半年内均匀消耗,日销量=500÷180≈2.78件/天,无需精确到日,按半年维度计算即可) |
| 单次订货成本(S) | 80元/次(固定成本,与进货量X无关,包括手续、差旅等) |
| 单位仓储成本(H) | 0.4元/件/月 → 半年仓储成本=0.4×6=2.4元/件(仓储成本与库存持有时间、数量正相关,需换算为半年单位) |
| 单次进货量(决策变量X) | 每次向厂家订X件,半年内订货次数=总需求量÷进货量=D/X=500/X次 |
总费用=订货成本+仓储成本,两者此消彼长,需找到平衡点:
最终总费用公式:
TC = 40000/X + 1.2X
基于此得出以下脚本
import tkinter as tk
from tkinter import ttk, messagebox
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import math
from matplotlib.figure import Figure
class InventoryOptimizer:
def __init__(self, root):
# 设置中文字体支持
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
self.root = root
self.root.title("仓储费用优化计算器")
self.root.geometry("900x700")
self.root.resizable(True, True)
# 创建主框架
main_frame = ttk.Frame(root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 创建输入框架
input_frame = ttk.LabelFrame(main_frame, text="输入参数", padding="10")
input_frame.pack(fill=tk.X, pady=(0, 10))
# 输入字段配置
input_fields = [
("总销售数量(件):", "total_quantity", "500"),
("销售周期(月):", "sales_months", "6"),
("单次订货手续费(元):", "order_cost", "80"),
("单件单月库存费用(元):", "storage_cost", "0.4")
]
# 存储输入变量
self.vars = {}
# 创建输入控件
for i, (label_text, var_name, default) in enumerate(input_fields):
ttk.Label(input_frame, text=label_text).grid(
row=i, column=0, padx=5, pady=5, sticky=tk.W
)
self.vars[var_name] = tk.StringVar(value=default)
ttk.Entry(input_frame, textvariable=self.vars[var_name], width=20).grid(
row=i, column=1, padx=5, pady=5, sticky=tk.W
)
# 计算按钮
btn_frame = ttk.Frame(input_frame)
btn_frame.grid(row=len(input_fields), column=0, columnspan=2, pady=10)
ttk.Button(btn_frame, text="计算最优方案", command=self.calculate).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="重置", command=self.reset).pack(side=tk.LEFT, padx=5)
# 创建结果框架
result_frame = ttk.LabelFrame(main_frame, text="计算结果", padding="10")
result_frame.pack(fill=tk.BOTH, expand=True)
# 结果面板
result_paned = ttk.PanedWindow(result_frame, orient=tk.VERTICAL)
result_paned.pack(fill=tk.BOTH, expand=True)
# 文本结果区域
text_frame = ttk.Frame(result_paned, height=150)
result_paned.add(text_frame, weight=1)
self.result_text = tk.Text(text_frame, wrap=tk.WORD, padx=5, pady=5)
self.result_text.pack(fill=tk.BOTH, expand=True)
self.result_text.config(state=tk.DISABLED)
# 图表区域
chart_frame = ttk.Frame(result_paned)
result_paned.add(chart_frame, weight=3)
# 创建图表
self.fig = Figure(figsize=(8, 4), dpi=100)
self.ax = self.fig.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.fig, master=chart_frame)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
def calculate(self):
"""计算最优订货量并显示结果"""
try:
# 获取输入参数
D = float(self.vars["total_quantity"].get()) # 总需求量
T = float(self.vars["sales_months"].get()) # 销售月数
S = float(self.vars["order_cost"].get()) # 单次订货成本
H_monthly = float(self.vars["storage_cost"].get()) # 单件单月库存成本
H = H_monthly * T # 单件总周期库存成本
# 计算最优订货量 (EOQ模型)
eoq = math.sqrt((2 * D * S) / H)
optimal_order_quantity = round(eoq)
# 计算相关成本
order_count = D / eoq # 订货次数
optimal_order_cost = order_count * S # 最优订货成本
optimal_storage_cost = (eoq / 2) * H # 最优库存成本
total_cost = optimal_order_cost + optimal_storage_cost # 总成本
# 生成图表数据
x_min = max(1, int(eoq * 0.3)) # 避免x值过小
x_max = int(eoq * 2)
x_values = np.linspace(x_min, x_max, 100) # 生成X值范围
# 计算不同订货量下的成本
order_costs = (D / x_values) * S # 订货成本
storage_costs = (x_values / 2) * H # 库存成本
total_costs = order_costs + storage_costs # 总成本
# 清空之前的图表
self.ax.clear()
# 绘制成本曲线
self.ax.plot(x_values, order_costs, label='订货成本', color='blue', linewidth=2)
self.ax.plot(x_values, storage_costs, label='库存成本', color='green', linewidth=2)
self.ax.plot(x_values, total_costs, label='总成本', color='red', linewidth=2)
# 标记最优订货量
self.ax.axvline(x=eoq, color='purple', linestyle='--',
label=f'最优订货量: {optimal_order_quantity}件')
# 图表设置
self.ax.set_xlabel('单次订货量 (件)')
self.ax.set_ylabel('成本 (元)')
self.ax.set_title('库存成本优化分析')
self.ax.legend()
self.ax.grid(True, linestyle='--', alpha=0.7)
self.fig.tight_layout()
# 更新画布
self.canvas.draw()
# 显示文本结果
self.result_text.config(state=tk.NORMAL)
self.result_text.delete(1.0, tk.END)
result = "仓储费用优化分析结果:\n\n"
result += f"1. 最优单次订货量:{optimal_order_quantity} 件\n"
result += f"2. 预计订货次数:{order_count:.1f} 次\n"
result += f"3. 订货总成本:{optimal_order_cost:.2f} 元\n"
result += f"4. 库存总成本:{optimal_storage_cost:.2f} 元\n"
result += f"5. 总费用:{total_cost:.2f} 元\n\n"
result += "分析:\n"
result += "- 当订货量小于最优值时,订货成本占主导,总费用随订货量增加而降低\n"
result += "- 当订货量大于最优值时,库存成本占主导,总费用随订货量增加而上升\n"
result += "- 最优订货量处,订货成本与库存成本基本相等,总费用最低"
self.result_text.insert(tk.END, result)
self.result_text.config(state=tk.DISABLED)
except ValueError:
messagebox.showerror("输入错误", "请确保所有输入都是有效的数字")
except Exception as e:
messagebox.showerror("计算错误", f"计算过程中出现错误: {str(e)}")
def reset(self):
"""重置输入和结果"""
for var in self.vars.values():
var.set("")
self.result_text.config(state=tk.NORMAL)
self.result_text.delete(1.0, tk.END)
self.result_text.config(state=tk.DISABLED)
self.ax.clear()
self.canvas.draw()
if __name__ == "__main__":
root = tk.Tk()
app = InventoryOptimizer(root)
root.mainloop()
这是我第二次做水煮肉片,肉片滑嫩鲜香,滋味香浓不腻,记录一下做法

食材
- 猪里脊
- 油麦菜
- 绿豆芽
- 包菜
- 炸响铃
- 冻豆腐
- 午餐肉
配料
- 菜籽油
- 郫县豆掰酱
- 生抽
- 盐
- 蚝油
- 姜(分别切姜末和姜片)
- 糖
- 火锅底料
- 干辣椒
- 辣椒粉
- 花椒
- 葱
- 蒜头
- 生粉
准备
- 猪里脊切薄片,盐、花椒、姜末、鸡蛋清抓匀至发黏,加入少量水淀粉(生粉+水),再次抓匀后加入适量菜籽油封住水分,腌制10-30分钟
- 油麦菜洗净切合适长度,茎秆部分切薄片,泡水10分钟后晾干备用
- 午餐肉切片备用
- 炸响铃、午餐肉,流水冲洗掉上面的浮灰,晾干备用
- 包菜手撕,和绿豆芽分别洗干净,泡水10分钟后晾干备用
- 葱切葱段和葱花,蒜切蒜泥,姜切姜片,这道菜葱可以多些,蒜泥不需要太多,需要突出麻辣香
烹调过程
- 锅内放微量菜籽油,开最小火,放入干辣椒、花椒,炒制干香。干辣椒和花椒虽然看上去很干,其实里面还是有很多水分的,这步操作非常重要。炒的时候一定要注意控制火候,如果感觉温度太高,可以将炒锅暂时离火,千万别炒焦了。等干辣椒变得非常脆的时候,取出,在干燥的砧板上,用菜刀切碎。刀口辣椒就做好了。将刀口辣椒加点细细的辣椒面混合,放在一旁备用。
- 锅不用洗,倒少量菜籽油,将绿豆芽和包菜放进去,加盐入底味,炒至断生,放入砂锅中
- 将炸响铃、午餐肉依次放入砂锅中,均匀铺开,再将油麦菜也铺在上面
- 起锅烧油,加入姜片和葱段,炒出香味后,放入郫县豆瓣酱小火继续翻炒至出红油,再加入火锅底料继续翻炒至底料差不多化开,加正好能没过配菜的清水,加生抽、盐、糖、蚝油。加盖烧开煮几分钟。这步几个要点,豆瓣酱一定要炒熟才不会有异味,加完调料后的汤汁偏咸一口,成品最佳。
- 用工具将料渣全部捞出,留下干净的汤汁,放入冻豆腐加盖煮熟后捞出放入砂锅
- 开始汆烫肉片,将锅内汤汁重新烧开,调最小火依次放入肉片,肉片放入锅内先不要动,避免脱浆,20-40秒之后,用锅铲轻轻推开肉片,防止粘连,这个时候可以适当调高火力,等肉片变色发白即可捞出均匀铺在砂锅里,这个过程约2分钟左右,千万不能时间太长,避免肉片烫老了口感不好。
- 将汤汁倒入砂锅中,开中火继续加热
- 将蒜泥、葱花、准备好的刀口辣椒都铺在肉上,起锅烧油,油稍多些要可以覆盖砂锅表面,油烧冒烟以后关火将热油浇在香料上。滋啦一声,香气四溢。
- 开吃

昨天写了一个小脚本,用Python帮我自动汇总业务人员的活动报告,节省了不少的时间。
俗话说,独乐乐 不如众乐乐,何不将其制作成可执行文件,分发给嘉智联的小伙伴们使用呢?
改写后的Rust脚本
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use anyhow::{Context, Result};
use calamine::{open_workbook, Reader, Xlsx};
use chrono::NaiveDateTime;
use eframe::egui;
use rust_xlsxwriter::{
Format, FormatAlign, FormatBorder, Workbook,
};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use walkdir::WalkDir;
// 线程安全的共享状态
struct SharedState {
log: Vec<String>,
status: String,
processing: bool,
}
#[derive(Clone)]
struct ExcelSummaryApp {
input_dir: String,
output_path: String,
shared_state: Arc<Mutex<SharedState>>,
}
impl Default for ExcelSummaryApp {
fn default() -> Self {
Self {
input_dir: String::new(),
output_path: String::new(),
shared_state: Arc::new(Mutex::new(SharedState {
log: Vec::new(),
status: String::new(),
processing: false,
})),
}
}
}
#[derive(Debug)]
struct ActivityInfo {
topic: String,
jzl_person: String,
location: String,
time: String,
organizer: String,
contact_person: String,
contact_number: String,
agenda: String,
summary: String,
source_file: String,
}
impl eframe::App for ExcelSummaryApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// 锁定共享状态以读取
let state = self.shared_state.lock().unwrap();
let status = state.status.clone();
let log = state.log.clone();
let processing = state.processing;
drop(state); // 提前释放锁
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("嘉智联活动信息汇总工具");
ui.separator();
// 输入文件夹选择
ui.horizontal(|ui| {
ui.label("输入文件夹:");
ui.text_edit_singleline(&mut self.input_dir);
if ui.button("浏览...").clicked() {
if let Some(path) = rfd::FileDialog::new().pick_folder() {
self.input_dir = path.to_string_lossy().to_string();
}
}
});
// 输出文件选择
ui.horizontal(|ui| {
ui.label("输出文件:");
ui.text_edit_singleline(&mut self.output_path);
if ui.button("浏览...").clicked() {
if let Some(path) = rfd::FileDialog::new()
.add_filter("Excel文件", &["xlsx"])
.save_file()
{
let path_str = path.to_string_lossy().to_string();
self.output_path = if path_str.ends_with(".xlsx") {
path_str
} else {
format!("{}.xlsx", path_str)
};
}
}
});
ui.separator();
// 处理按钮
ui.horizontal(|ui| {
let button_enabled = !processing
&& !self.input_dir.is_empty()
&& !self.output_path.is_empty()
&& Path::new(&self.input_dir).exists();
let button = ui.add_enabled(button_enabled, egui::Button::new("开始汇总").min_size(egui::vec2(120.0, 30.0)));
if button.clicked() {
// 更新状态为处理中
let mut state = self.shared_state.lock().unwrap();
state.processing = true;
state.status = "正在处理...".to_string();
state.log.clear();
drop(state); // 释放锁
// 克隆必要的数据以传递给线程
let input_dir = self.input_dir.clone();
let output_path = self.output_path.clone();
let shared_state = Arc::clone(&self.shared_state);
// 在新线程中处理,避免UI卡顿
std::thread::spawn(move || {
let mut log = Vec::new();
let result = process_excel_files(&input_dir, &output_path, &mut log);
// 更新状态
let mut state = shared_state.lock().unwrap();
state.status = match result {
Ok(_) => "处理完成!".to_string(),
Err(e) => format!("处理失败: {}", e),
};
state.log = log;
state.processing = false;
});
}
});
// 状态显示
ui.label(&status);
// 日志显示
ui.separator();
ui.label("处理日志:");
egui::ScrollArea::vertical().show(ui, |ui| {
for line in &log {
ui.label(line);
}
});
});
// 每帧请求重绘以更新状态
ctx.request_repaint();
}
}
fn process_excel_files(input_dir: &str, output_path: &str, log: &mut Vec<String>) -> Result<()> {
log.push(format!("开始处理文件夹: {}", input_dir));
// 查找所有Excel文件
let excel_files: Vec<PathBuf> = WalkDir::new(input_dir)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| {
e.file_type().is_file() &&
matches!(e.path().extension().and_then(|s| s.to_str()),
Some("xlsx") | Some("xls")) &&
!e.path().file_name().and_then(|s| s.to_str()).map_or(false, |n| n.contains("汇总"))
})
.map(|e| e.path().to_path_buf())
.collect();
log.push(format!("找到 {} 个Excel文件", excel_files.len()));
if excel_files.is_empty() {
return Ok(());
}
// 提取每个文件的信息
let mut activities = Vec::new();
for path in &excel_files {
log.push(format!("处理文件: {}", path.to_string_lossy()));
match extract_activity_info(path) {
Ok(info) => activities.push(info),
Err(e) => log.push(format!("处理文件 {} 时出错: {}", path.to_string_lossy(), e)),
}
}
// 生成输出Excel
generate_output_excel(&activities, output_path, log)?;
log.push(format!("汇总完成,结果保存至: {}", output_path));
Ok(())
}
fn extract_activity_info(path: &Path) -> Result<ActivityInfo> {
let mut workbook: Xlsx<_> = open_workbook(path)
.with_context(|| format!("无法打开文件: {}", path.to_string_lossy()))?;
let sheet_names = workbook.sheet_names().to_vec();
if sheet_names.is_empty() {
return Err(anyhow::anyhow!("文件中没有工作表"));
}
let first_sheet = &sheet_names[0];
let range = workbook.worksheet_range(first_sheet)
.with_context(|| format!("无法读取工作表: {}", first_sheet))?;
let max_row = range.height() as usize;
// 辅助函数:安全获取单元格值,使用具体的Xlsx数据类型
fn get_cell_value(range: &calamine::Range<calamine::Data>, row: usize, col: usize) -> String {
if row < range.height() as usize && col < range.width() as usize {
match range.get((row, col)).unwrap_or(&calamine::Data::Empty) {
calamine::Data::String(s) => s.clone(),
calamine::Data::Float(f) => f.to_string(),
calamine::Data::Int(i) => i.to_string(),
calamine::Data::Bool(b) => b.to_string(),
calamine::Data::DateTime(d) => d.to_string(),
calamine::Data::Empty => String::new(),
calamine::Data::Error(_) => String::new(),
calamine::Data::DateTimeIso(s) => s.clone(),
calamine::Data::DurationIso(s) => s.clone(),
}
} else {
String::new()
}
}
// 查找"嘉智联渠道市场推广活动报告"所在列
let topic_col = (0..range.width() as usize)
.find(|&col| get_cell_value(&range, 0, col).contains("嘉智联渠道市场推广活动报告"))
.ok_or_else(|| anyhow::anyhow!("未找到标题列"))?;
// 查找各Unnamed列(根据实际数据分布估算)
let unnamed_2_col = topic_col + 2;
let unnamed_5_col = topic_col + 5;
let unnamed_7_col = topic_col + 7;
let unnamed_8_col = topic_col + 8;
// 提取所需内容,增加索引检查(与Python脚本保持一致)
let activity_topic = if 2 < max_row {
get_cell_value(&range, 2, topic_col)
} else {
String::new()
};
let jzl_person_in_charge = if 2 < max_row {
get_cell_value(&range, 2, unnamed_8_col)
} else {
String::new()
};
let mut activity_location = if 5 < max_row {
get_cell_value(&range, 5, topic_col)
} else {
String::new()
};
// 移除活动地点中的下划线
activity_location = activity_location.replace('_', "");
let activity_time = if 5 < max_row {
get_cell_value(&range, 5, unnamed_5_col)
} else {
String::new()
};
let organizer = if 8 < max_row {
get_cell_value(&range, 8, topic_col)
} else {
String::new()
};
let contact_person = if 8 < max_row {
get_cell_value(&range, 8, unnamed_7_col)
} else {
String::new()
};
// 根据测试结果修正:联系电话在第8行,Unnamed:7列
let contact_number = if 9 < max_row {
get_cell_value(&range, 9, unnamed_7_col)
} else {
String::new()
};
// 活动议程部分
let mut activity_agenda = String::new();
let agenda_start = 12;
let agenda_end = 19;
if agenda_start < max_row {
let actual_end = std::cmp::min(agenda_end, max_row - 1);
let mut agenda_items = Vec::new();
for row in agenda_start..=actual_end {
let time = get_cell_value(&range, row, topic_col);
let agenda = get_cell_value(&range, row, unnamed_2_col);
let person = get_cell_value(&range, row, unnamed_7_col);
// 处理时间格式,将Excel数字格式转换为时间格式
let formatted_time = if let Ok(numeric_time) = time.parse::<f64>() {
// 如果是数字格式的时间,转换为小时:分钟格式
let hours = (numeric_time * 24.0).floor() as u32;
let minutes = ((numeric_time * 24.0 * 60.0) % 60.0).round() as u32;
format!("{:02}:{:02}", hours % 24, minutes)
} else {
time.clone()
};
if !formatted_time.is_empty() || !agenda.is_empty() {
if !person.is_empty() {
agenda_items.push(format!("{} - {}({})", formatted_time, agenda, person));
} else {
agenda_items.push(format!("{} - {}", formatted_time, agenda));
}
}
}
activity_agenda = agenda_items.join(";");
}
// 活动小结 - 从36行到43行(index=35到42)和A列到J列(index=0到9)提取内容
let mut activity_summary = String::new();
let summary_start = 35;
let summary_end = 42;
let col_start = 0;
let col_end = 9;
if summary_start < max_row {
let actual_summary_end = std::cmp::min(summary_end, max_row - 1);
let actual_col_end = std::cmp::min(col_end, range.width() as usize - 1);
let mut summary_cells = Vec::new();
for row_idx in summary_start..=actual_summary_end {
for col_idx in col_start..=actual_col_end {
let cell_value = get_cell_value(&range, row_idx, col_idx);
if !cell_value.trim().is_empty() {
summary_cells.push(cell_value.trim().to_string());
}
}
}
activity_summary = summary_cells.join(" ");
}
Ok(ActivityInfo {
topic: activity_topic.trim().to_string(),
jzl_person: jzl_person_in_charge.trim().to_string(),
location: activity_location.trim().to_string(),
time: activity_time.trim().to_string(),
organizer: organizer.trim().to_string(),
contact_person: contact_person.trim().to_string(),
contact_number: contact_number.trim().to_string(),
agenda: activity_agenda,
summary: activity_summary,
source_file: path.file_name().and_then(|n| n.to_str()).unwrap_or("").to_string(),
})
}
fn generate_output_excel(activities: &[ActivityInfo], output_path: &str, _log: &mut Vec<String>) -> Result<()> {
let mut workbook = Workbook::new();
// 创建格式 - 使用构建器模式避免所有权问题
let header_format = Format::new()
.set_bold()
.set_border(FormatBorder::Thin)
.set_align(FormatAlign::Center);
let default_format = Format::new()
.set_border(FormatBorder::Thin)
.set_text_wrap();
let date_format = Format::new()
.set_border(FormatBorder::Thin)
.set_text_wrap()
.set_align(FormatAlign::Center)
.set_num_format("yyyy-mm-dd hh:mm");
let phone_format = Format::new()
.set_border(FormatBorder::Thin)
.set_text_wrap()
.set_num_format("@");
let agenda_format = Format::new()
.set_border(FormatBorder::Thin)
.set_text_wrap();
// 添加工作表
let worksheet = workbook.add_worksheet();
// 设置列宽
worksheet.set_column_width(0, 30.0)?; // 活动主题
worksheet.set_column_width(1, 12.0)?; // 嘉智联担当
worksheet.set_column_width(2, 25.0)?; // 活动地点
worksheet.set_column_width(3, 20.0)?; // 活动时间
worksheet.set_column_width(4, 15.0)?; // 主办单位
worksheet.set_column_width(5, 12.0)?; // 联系人
worksheet.set_column_width(6, 15.0)?; // 联系电话
worksheet.set_column_width(7, 40.0)?; // 活动议程
worksheet.set_column_width(8, 50.0)?; // 活动小结
worksheet.set_column_width(9, 20.0)?; // 来源文件
// 写入表头
let headers = [
"活动主题", "嘉智联担当", "活动地点", "活动时间", "主办单位",
"联系人", "联系电话", "活动议程", "活动小结", "原始报告"
];
for (col, header) in headers.iter().enumerate() {
worksheet.write_with_format(0, col as u16, *header, &header_format)?;
}
// 写入数据
for (row_idx, activity) in activities.iter().enumerate() {
let row = (row_idx + 1) as u32;
worksheet.write_with_format(row, 0, &activity.topic, &default_format)?;
worksheet.write_with_format(row, 1, &activity.jzl_person, &default_format)?;
worksheet.write_with_format(row, 2, &activity.location, &default_format)?;
// 尝试解析日期
if let Ok(date) = NaiveDateTime::parse_from_str(&activity.time, "%Y年%m月%d日 %H:%M") {
worksheet.write_with_format(row, 3, date.to_string(), &date_format)?;
} else {
worksheet.write_with_format(row, 3, &activity.time, &date_format)?;
}
worksheet.write_with_format(row, 4, &activity.organizer, &default_format)?;
worksheet.write_with_format(row, 5, &activity.contact_person, &default_format)?;
worksheet.write_with_format(row, 6, &activity.contact_number, &phone_format)?;
// 使用专用格式写入活动议程列,并将分号替换为换行符以实现真正的自动换行
let formatted_agenda = activity.agenda.replace(";", "\n");
worksheet.write_with_format(row, 7, &formatted_agenda, &agenda_format)?;
worksheet.write_with_format(row, 8, &activity.summary, &default_format)?;
worksheet.write_with_format(row, 9, &activity.source_file, &default_format)?;
}
// 保存文件
workbook.save(output_path).with_context(|| format!("无法保存文件: {}", output_path))?;
Ok(())
}
fn main() -> Result<()> {
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(800.0, 600.0)),
..Default::default()
};
let _ = eframe::run_native(
"嘉智联活动信息汇总工具",
options,
Box::new(|cc| {
// 设置中文字体支持 - 动态检测并使用系统字体
setup_fonts(cc);
Box::new(ExcelSummaryApp::default())
}),
);
Ok(())
}
// 设置字体的函数
fn setup_fonts(cc: &eframe::CreationContext<'_>) {
// 获取系统字体
let mut fonts = egui::FontDefinitions::default();
// 在Windows系统上尝试使用系统默认中文字体
if cfg!(target_os = "windows") {
// Windows系统常见的中文字体路径
let system_fonts = [
"C:\\Windows\\Fonts\\msyh.ttc", // 微软雅黑
"C:\\Windows\\Fonts\\msyh.ttf", // 微软雅黑
"C:\\Windows\\Fonts\\simhei.ttf", // 黑体
"C:\\Windows\\Fonts\\simsun.ttc", // 宋体
"C:\\Windows\\Fonts\\simkai.ttf", // 楷体
];
// 检查系统字体文件是否存在,如果存在则使用
for font_path in &system_fonts {
if std::path::Path::new(font_path).exists() {
match std::fs::read(font_path) {
Ok(font_data) => {
fonts.font_data.insert(
"SystemChineseFont".to_owned(),
egui::FontData::from_owned(font_data),
);
fonts.families.get_mut(&egui::FontFamily::Proportional).unwrap()
.insert(0, "SystemChineseFont".to_owned());
fonts.families.get_mut(&egui::FontFamily::Monospace).unwrap()
.insert(0, "SystemChineseFont".to_owned());
// 找到第一个可用字体就退出循环
break;
}
Err(_) => continue,
}
}
}
}
cc.egui_ctx.set_fonts(fonts);
}
最终生成exe文件,文件大小仅仅4335K,以及超快的运行速度,这是Python无法比拟的优势。
import pandas as pd
import os
import fnmatch
def extract_summary_info(excel_path):
try:
# 读取文件
excel_file = pd.ExcelFile(excel_path)
# 获取所有表名
sheet_names = excel_file.sheet_names
if not sheet_names:
raise ValueError(f"文件 {excel_path} 中没有工作表")
# 获取指定工作表中的数据
df = excel_file.parse(sheet_names[0])
# 提取所需内容
activity_topic = df.loc[1, '嘉智联渠道市场推广活动报告']
jzl_person_in_charge = df.loc[1, 'Unnamed: 8']
activity_location = df.loc[4, '嘉智联渠道市场推广活动报告']
activity_time = df.loc[4, 'Unnamed: 5']
organizer = df.loc[7, '嘉智联渠道市场推广活动报告']
contact_person = df.loc[7, 'Unnamed: 7']
contact_number = df.loc[8, 'Unnamed: 8']
# 活动议程部分,从第 12 行(index=11)到第 15 行(index=14)获取时间和议程信息
agenda_rows = df.loc[11:14, ['嘉智联渠道市场推广活动报告', 'Unnamed: 2', 'Unnamed: 7']]
agenda_rows.columns = ['开始时间', '议程', '负责担当']
activity_agenda = ';'.join([
f"{row['开始时间']} - {row['议程']}({row['负责担当']})" if pd.notna(row['负责担当'])
else f"{row['开始时间']} - {row['议程']}" for _, row in agenda_rows.iterrows()
])
activity_summary = df.loc[35, '嘉智联渠道市场推广活动报告']
# 创建汇总数据的 DataFrame
data = {
'活动主题': [activity_topic],
'嘉智联担当': [jzl_person_in_charge],
'活动地点': [activity_location],
'活动时间': [activity_time],
'主办单位': [organizer],
'联系人': [contact_person],
'联系电话': [contact_number],
'活动议程': [activity_agenda],
'活动小结': [activity_summary]
}
result_df = pd.DataFrame(data)
return result_df
except Exception as e:
print(f"处理文件 {excel_path} 时出现错误: {e}")
return pd.DataFrame()
def find_excel_files(root_folder):
excel_files = []
for root, dirs, files in os.walk(root_folder):
for file in files:
if fnmatch.fnmatch(file, '*.xlsx') or fnmatch.fnmatch(file, '*.xls'):
excel_files.append(os.path.join(root, file))
return excel_files
def main(file_list, result_file_path):
dfs = []
excel_files = file_list
for excel_file in excel_files:
result_df = extract_summary_info(excel_file)
dfs.append(result_df)
# 循环结束后一次性合并 DataFrame
if dfs:
result = pd.concat(dfs, ignore_index=True)
result.to_excel(result_file_path, index=False)
print(result)
else:
print("没有找到有效的数据")
if __name__ == '__main__':
path = r".\会议资料2507"
main(find_excel_files(path), r".\会议汇总2507.xlsx")