1abac4e28d
- 新增 try_parse_datetime 函数用于解析多种日期时间格式 - 实现 datetime_to_excel_serial 函数将 Chrono 时间转换为 Excel 序列号 - 添加 excel_serial_to_datetime 函数将 Excel 序列号转回 Chrono 时间 - 在 insert_excel 函数中添加日期解析和格式化写入功能 - 在 update_excel 函数中集成日期时间处理逻辑 - 修改 get_cell_value 函数支持日期格式化显示 - 优化日期相关错误处理和边界情况检查
601 lines
19 KiB
Rust
601 lines
19 KiB
Rust
use clap::{Parser, Subcommand};
|
||
use std::path::PathBuf;
|
||
use umya_spreadsheet::*;
|
||
use std::fs;
|
||
use std::io::Write;
|
||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||
|
||
// --- 日期时间辅助逻辑 (新增) ---
|
||
|
||
/// 尝试将字符串解析为日期时间,并返回对应的 Excel 格式代码
|
||
fn try_parse_datetime(s: &str) -> Option<(NaiveDateTime, &'static str)> {
|
||
const FORMATS: &[(&str, &str)] = &[
|
||
("%Y-%m-%d %H:%M:%S", "yyyy-mm-dd hh:mm:ss"),
|
||
("%Y/%m/%d %H:%M:%S", "yyyy/mm/dd hh:mm:ss"),
|
||
("%Y-%m-%d", "yyyy-mm-dd"),
|
||
("%Y/%m/%d", "yyyy/mm/dd"),
|
||
("%H:%M:%S", "hh:mm:ss"),
|
||
("%H:%M", "hh:mm"),
|
||
];
|
||
|
||
for (fmt_str, excel_fmt) in FORMATS {
|
||
// 1. 尝试解析为完整 DateTime
|
||
if let Ok(dt) = NaiveDateTime::parse_from_str(s, fmt_str) {
|
||
return Some((dt, excel_fmt));
|
||
}
|
||
// 2. 尝试解析为 Date (默认时间 00:00:00)
|
||
if let Ok(d) = NaiveDate::parse_from_str(s, fmt_str) {
|
||
let dt = d.and_hms_opt(0, 0, 0).unwrap();
|
||
return Some((dt, excel_fmt));
|
||
}
|
||
// 3. 尝试解析为 Time (默认日期 1899-12-30,Excel 纪元)
|
||
if let Ok(t) = NaiveTime::parse_from_str(s, fmt_str) {
|
||
let dt = NaiveDate::from_ymd_opt(1899, 12, 30).unwrap().and_time(t);
|
||
return Some((dt, excel_fmt));
|
||
}
|
||
}
|
||
None
|
||
}
|
||
|
||
/// 将 Chrono 时间转换为 Excel 序列号 (f64)
|
||
fn datetime_to_excel_serial(dt: &NaiveDateTime) -> f64 {
|
||
// Excel 的 Windows 纪元是 1899-12-30 (由于历史 Bug)
|
||
let excel_epoch = NaiveDate::from_ymd_opt(1899, 12, 30)
|
||
.unwrap()
|
||
.and_hms_opt(0, 0, 0)
|
||
.unwrap();
|
||
|
||
let duration = dt.signed_duration_since(excel_epoch);
|
||
let days = duration.num_days() as f64;
|
||
let secs = (duration.num_seconds() % 86400).abs() as f64;
|
||
|
||
days + (secs / 86400.0)
|
||
}
|
||
|
||
/// 将 Excel 序列号转换回 Chrono 时间
|
||
fn excel_serial_to_datetime(serial: f64) -> Option<NaiveDateTime> {
|
||
let excel_epoch = NaiveDate::from_ymd_opt(1899, 12, 30)
|
||
.unwrap()
|
||
.and_hms_opt(0, 0, 0)
|
||
.unwrap();
|
||
|
||
let days = serial.trunc() as i64;
|
||
let fraction = serial.fract().abs();
|
||
let secs = (fraction * 86400.0).round() as i64;
|
||
|
||
excel_epoch
|
||
.checked_add_signed(chrono::Duration::days(days))?
|
||
.checked_add_signed(chrono::Duration::seconds(secs))
|
||
}
|
||
|
||
// --- 错误处理 (原有) ---
|
||
|
||
#[derive(Debug, thiserror::Error)]
|
||
enum DexcelError {
|
||
#[error("IO错误: {0}")]
|
||
Io(#[from] std::io::Error),
|
||
#[error("Excel错误: {0}")]
|
||
Umya(String),
|
||
#[error("操作错误: {0}")]
|
||
Operation(String),
|
||
}
|
||
|
||
type Result<T> = std::result::Result<T, DexcelError>;
|
||
|
||
// --- CLI 定义 (原有,未改动) ---
|
||
|
||
#[derive(Parser)]
|
||
#[command(name = "dexcel")]
|
||
#[command(about = "Excel 读写工具 (样式保留版)", long_about = None)]
|
||
struct Cli {
|
||
/// Excel 文件路径
|
||
#[arg(required_if_eq("command", "query"))]
|
||
#[arg(required_if_eq("command", "count"))]
|
||
#[arg(required_if_eq("command", "insert"))]
|
||
#[arg(required_if_eq("command", "update"))]
|
||
#[arg(required_if_eq("command", "delete"))]
|
||
#[arg(required_if_eq("command", "style"))]
|
||
file: Option<PathBuf>,
|
||
|
||
#[command(subcommand)]
|
||
command: Commands,
|
||
}
|
||
|
||
#[derive(Subcommand)]
|
||
enum Commands {
|
||
/// 查询数据 (只读)
|
||
Query {
|
||
#[arg(default_value_t = 0, allow_hyphen_values = true)]
|
||
count: i32,
|
||
#[arg(long, allow_hyphen_values = true)]
|
||
start: Option<i32>,
|
||
#[arg(long)]
|
||
last: bool,
|
||
#[arg(long)]
|
||
sheet: Option<String>,
|
||
},
|
||
/// 统计行数
|
||
Count {
|
||
#[arg(long)]
|
||
sheet: Option<String>,
|
||
},
|
||
/// 创建新文件
|
||
New {
|
||
name: String,
|
||
#[arg(long)]
|
||
sheet: Option<String>,
|
||
},
|
||
/// 插入行 (保留样式)
|
||
Insert {
|
||
/// 要插入的值 (用 | 分隔)
|
||
value: String,
|
||
/// 插入位置 (空则追加)
|
||
#[arg(long, allow_hyphen_values = true)]
|
||
row: Option<i32>,
|
||
#[arg(long)]
|
||
sheet: Option<String>,
|
||
#[arg(long, default_value = "|")]
|
||
split: String,
|
||
},
|
||
/// 更新数据 (保留样式)
|
||
Update {
|
||
/// 新值
|
||
value: String,
|
||
/// 行号
|
||
#[arg(long, allow_hyphen_values = true)]
|
||
row: i32,
|
||
/// 起始列号 (默认1)
|
||
#[arg(long)]
|
||
cell: Option<u32>,
|
||
#[arg(long)]
|
||
sheet: Option<String>,
|
||
#[arg(long, default_value = "|")]
|
||
split: String,
|
||
},
|
||
/// 删除行
|
||
Delete {
|
||
/// 起始行号(支持负数)
|
||
#[arg(allow_hyphen_values = true)]
|
||
start: i32,
|
||
/// 删除的行数(默认1)
|
||
#[arg(long, default_value_t = 1)]
|
||
count: u32,
|
||
/// 跳过确认
|
||
#[arg(long, short = 'y')]
|
||
yes: bool,
|
||
#[arg(long)]
|
||
sheet: Option<String>,
|
||
},
|
||
/// 设置样式
|
||
Style {
|
||
#[arg(long, allow_hyphen_values = true)]
|
||
rows: Vec<i32>,
|
||
#[arg(long)]
|
||
cols: Vec<u32>,
|
||
#[arg(long)]
|
||
row_height: Option<f64>,
|
||
#[arg(long)]
|
||
col_width: Option<f64>,
|
||
#[arg(long)]
|
||
wrap_text: bool,
|
||
#[arg(long)]
|
||
sheet: Option<String>,
|
||
},
|
||
}
|
||
|
||
// --- 核心工具函数 (原有 + 修改 get_cell_value) ---
|
||
|
||
// 安全保存辅助
|
||
fn safe_save_umya(book: &mut Spreadsheet, target: &PathBuf) -> Result<()> {
|
||
let backup = if target.exists() {
|
||
let backup = target.with_extension("xlsx.bak");
|
||
let _ = fs::remove_file(&backup);
|
||
fs::copy(target, &backup)?;
|
||
Some(backup)
|
||
} else {
|
||
None
|
||
};
|
||
|
||
// 尝试写入文件
|
||
match writer::xlsx::write(book, target) {
|
||
Ok(_) => {
|
||
// 写入成功,删除备份文件
|
||
if let Some(bak) = backup {
|
||
let _ = fs::remove_file(bak);
|
||
}
|
||
Ok(())
|
||
}
|
||
Err(e) => {
|
||
// 写入失败,恢复备份
|
||
if let Some(bak) = &backup {
|
||
if bak.exists() {
|
||
let _ = fs::copy(bak, target);
|
||
}
|
||
}
|
||
Err(DexcelError::Umya(e.to_string()))
|
||
}
|
||
}
|
||
}
|
||
|
||
fn get_sheet_mut<'a>(book: &'a mut Spreadsheet, name: &Option<String>) -> Result<&'a mut Worksheet> {
|
||
match name {
|
||
Some(n) => book.get_sheet_by_name_mut(n).ok_or_else(|| DexcelError::Operation(format!("找不到Sheet: {}", n))),
|
||
None => Ok(book.get_sheet_mut(&0).ok_or_else(|| DexcelError::Operation("找不到Sheet".to_string()))?),
|
||
}
|
||
}
|
||
|
||
fn get_sheet<'a>(book: &'a Spreadsheet, name: &Option<String>) -> Result<&'a Worksheet> {
|
||
match name {
|
||
Some(n) => book.get_sheet_by_name(n).ok_or_else(|| DexcelError::Operation(format!("找不到Sheet: {}", n))),
|
||
None => Ok(book.get_sheet(&0).ok_or_else(|| DexcelError::Operation("找不到Sheet".to_string()))?),
|
||
}
|
||
}
|
||
|
||
// 解析行号 (1-based -> 0-based)
|
||
fn resolve_row(input: i32, max: usize) -> Result<usize> {
|
||
let max_i = max as i32;
|
||
let idx = if input < 0 { max_i + input } else { input - 1 };
|
||
|
||
if idx < 0 || idx >= max_i {
|
||
Err(DexcelError::Operation(format!("行号 {} 超出范围", input)))
|
||
} else {
|
||
Ok(idx as usize)
|
||
}
|
||
}
|
||
|
||
// --- 修改版:获取单元格值 (支持日期格式化显示) ---
|
||
fn get_cell_value(sheet: &Worksheet, row: usize, col: usize) -> String {
|
||
// 坐标是 (col, row) 1-based
|
||
let coord = (col as u32 + 1, row as u32 + 1);
|
||
|
||
if let Some(c) = sheet.get_cell(coord) {
|
||
let value = c.get_value();
|
||
|
||
// 尝试判断是否为日期格式的数字
|
||
if let Some(serial) = c.get_value_number() {
|
||
if let Some(num_fmt) = c.get_style().get_number_format() {
|
||
let fmt_code = num_fmt.get_format_code();
|
||
|
||
// 简单 heuristic:如果格式码包含日期关键字,尝试转换
|
||
let is_date_like = fmt_code.contains("yyyy") || fmt_code.contains("mm") || fmt_code.contains("dd") ||
|
||
fmt_code.contains("hh") || fmt_code.contains("ss");
|
||
|
||
if is_date_like {
|
||
if let Some(dt) = excel_serial_to_datetime(serial) {
|
||
// 根据 Excel 格式的类型返回不同的 Rust 格式
|
||
if fmt_code.contains("yyyy") && fmt_code.contains("hh") {
|
||
return dt.format("%Y-%m-%d %H:%M:%S").to_string();
|
||
} else if fmt_code.contains("yyyy") {
|
||
return dt.format("%Y-%m-%d").to_string();
|
||
} else if fmt_code.contains("hh") && fmt_code.contains("ss") {
|
||
return dt.format("%H:%M:%S").to_string();
|
||
} else if fmt_code.contains("hh") {
|
||
return dt.format("%H:%M").to_string();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 默认返回
|
||
value.to_string()
|
||
} else {
|
||
String::new()
|
||
}
|
||
}
|
||
|
||
// --- Main 入口 (原有,未改动) ---
|
||
|
||
fn main() -> Result<()> {
|
||
let cli = Cli::parse();
|
||
let result = match &cli.command {
|
||
Commands::Query { count, start, last, sheet } => {
|
||
let file = cli.file.as_ref().unwrap();
|
||
let book = reader::xlsx::read(file).map_err(|e| DexcelError::Umya(e.to_string()))?;
|
||
query_excel(&book, *count, *start, *last, sheet)
|
||
}
|
||
Commands::Count { sheet } => {
|
||
let file = cli.file.as_ref().unwrap();
|
||
let book = reader::xlsx::read(file).map_err(|e| DexcelError::Umya(e.to_string()))?;
|
||
count_excel(&book, sheet)
|
||
}
|
||
Commands::New { name, sheet } => {
|
||
let path = if name.ends_with(".xlsx") { PathBuf::from(name) } else { PathBuf::from(format!("{}.xlsx", name)) };
|
||
let mut book = new_file();
|
||
if let Some(s) = sheet {
|
||
book.get_sheet_mut(&0).unwrap().set_name(s);
|
||
}
|
||
writer::xlsx::write(&book, &path).map_err(|e| DexcelError::Umya(e.to_string()))?;
|
||
println!("✓ 已创建: {}", path.display());
|
||
Ok(())
|
||
}
|
||
Commands::Insert { value, row, sheet, split } => {
|
||
let file = cli.file.as_ref().unwrap();
|
||
let mut book = reader::xlsx::read(file).map_err(|e| DexcelError::Umya(e.to_string()))?;
|
||
insert_excel(&mut book, value, *row, sheet, split)?;
|
||
safe_save_umya(&mut book, file)
|
||
}
|
||
Commands::Update { value, row, cell, sheet, split } => {
|
||
let file = cli.file.as_ref().unwrap();
|
||
let mut book = reader::xlsx::read(file).map_err(|e| DexcelError::Umya(e.to_string()))?;
|
||
update_excel(&mut book, value, *row, *cell, sheet, split)?;
|
||
safe_save_umya(&mut book, file)
|
||
}
|
||
Commands::Delete { start, count, yes, sheet } => {
|
||
let file = cli.file.as_ref().unwrap();
|
||
let mut book = reader::xlsx::read(file).map_err(|e| DexcelError::Umya(e.to_string()))?;
|
||
delete_excel(&mut book, *start, *count, *yes, sheet)?;
|
||
safe_save_umya(&mut book, file)
|
||
}
|
||
Commands::Style { rows, cols, row_height, col_width, wrap_text, sheet } => {
|
||
let file = cli.file.as_ref().unwrap();
|
||
let mut book = reader::xlsx::read(file).map_err(|e| DexcelError::Umya(e.to_string()))?;
|
||
style_excel(&mut book, rows, cols, *row_height, *col_width, *wrap_text, sheet)?;
|
||
safe_save_umya(&mut book, file)
|
||
}
|
||
};
|
||
|
||
if let Err(e) = result {
|
||
eprintln!("❌ 错误: {}", e);
|
||
std::process::exit(1);
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
// --- 具体实现 (修改了 Insert 和 Update 的写入逻辑) ---
|
||
|
||
fn query_excel(book: &Spreadsheet, count: i32, start: Option<i32>, last: bool, sheet_name: &Option<String>) -> Result<()> {
|
||
let sheet = get_sheet(book, sheet_name)?;
|
||
let (_, max_row) = sheet.get_highest_column_and_row(); // 注意:umya 返回 (col, row)
|
||
let total_rows = max_row as usize;
|
||
|
||
if total_rows == 0 {
|
||
println!("表格为空");
|
||
return Ok(());
|
||
}
|
||
|
||
// 计算范围逻辑
|
||
let (start_row, end_row) = if last {
|
||
(total_rows - 1, total_rows)
|
||
} else if let Some(s) = start {
|
||
let actual_start = if s < 0 {
|
||
let pos = total_rows as i32 + s;
|
||
if pos < 0 { 0 } else { pos as usize }
|
||
} else {
|
||
(s as usize).saturating_sub(1)
|
||
};
|
||
|
||
if count > 0 {
|
||
(actual_start, (actual_start + count as usize).min(total_rows))
|
||
} else if count < 0 {
|
||
(actual_start, (actual_start + (-count) as usize).min(total_rows))
|
||
} else {
|
||
let max = 20;
|
||
(actual_start, (actual_start + max).min(total_rows))
|
||
}
|
||
} else if count > 0 {
|
||
(0, count as usize)
|
||
} else if count < 0 {
|
||
let abs = (-count) as usize;
|
||
let start = if abs >= total_rows { 0 } else { total_rows - abs };
|
||
(start, total_rows)
|
||
} else {
|
||
let max = 20;
|
||
((total_rows - max).max(0), total_rows)
|
||
};
|
||
|
||
let (max_col, _) = sheet.get_highest_column_and_row();
|
||
|
||
use tabled::builder::Builder;
|
||
let mut builder = Builder::default();
|
||
|
||
for r in start_row..end_row.min(total_rows) {
|
||
let row: Vec<String> = (0..max_col as usize)
|
||
.map(|c| get_cell_value(sheet, r, c))
|
||
.collect();
|
||
builder.push_record(row);
|
||
}
|
||
|
||
let mut table = builder.build();
|
||
table.with(tabled::settings::Style::modern());
|
||
println!("{}", table);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn count_excel(book: &Spreadsheet, sheet_name: &Option<String>) -> Result<()> {
|
||
let sheet = get_sheet(book, sheet_name)?;
|
||
let (_, row) = sheet.get_highest_column_and_row();
|
||
println!("{}", row);
|
||
Ok(())
|
||
}
|
||
|
||
// --- 修改版:插入行 (支持日期写入) ---
|
||
fn insert_excel(
|
||
book: &mut Spreadsheet,
|
||
value: &str,
|
||
row: Option<i32>,
|
||
sheet_name: &Option<String>,
|
||
split: &str,
|
||
) -> Result<()> {
|
||
let sheet = get_sheet_mut(book, sheet_name)?;
|
||
let (_, max_row) = sheet.get_highest_column_and_row();
|
||
|
||
// 确定插入位置 (umya 是 1-based)
|
||
let insert_idx_1based = match row {
|
||
Some(r) => {
|
||
if r < 0 {
|
||
(max_row as i32 + r + 2).max(1) as u32
|
||
} else {
|
||
r.max(1) as u32
|
||
}
|
||
}
|
||
None => max_row + 1,
|
||
};
|
||
|
||
if insert_idx_1based <= max_row {
|
||
sheet.insert_new_row(&insert_idx_1based, &1);
|
||
}
|
||
|
||
// 写入数据 (逻辑增强)
|
||
let vals: Vec<&str> = value.split(split).collect();
|
||
for (i, val) in vals.iter().enumerate() {
|
||
let col = (i + 1) as u32;
|
||
let cell = sheet.get_cell_mut((col, insert_idx_1based));
|
||
let val_trimmed = val.trim();
|
||
|
||
if !val_trimmed.is_empty() {
|
||
// 1. 尝试解析日期
|
||
if let Some((dt, excel_fmt)) = try_parse_datetime(val_trimmed) {
|
||
let serial = datetime_to_excel_serial(&dt);
|
||
cell.set_value_number(serial);
|
||
// 关键:设置单元格格式,这样 Excel 才会把它当日期看
|
||
cell.get_style_mut().get_number_format_mut().set_format_code(excel_fmt);
|
||
} else {
|
||
// 2. 写入文本
|
||
cell.set_value(val_trimmed);
|
||
}
|
||
}
|
||
}
|
||
|
||
println!("✓ 已在第 {} 行插入数据 (样式已保留)", insert_idx_1based);
|
||
Ok(())
|
||
}
|
||
|
||
// --- 修改版:更新行 (支持日期写入) ---
|
||
fn update_excel(
|
||
book: &mut Spreadsheet,
|
||
value: &str,
|
||
row: i32,
|
||
cell: Option<u32>,
|
||
sheet_name: &Option<String>,
|
||
split: &str,
|
||
) -> Result<()> {
|
||
let sheet = get_sheet_mut(book, sheet_name)?;
|
||
let (_, max_row) = sheet.get_highest_column_and_row();
|
||
|
||
let target_row = resolve_row(row, max_row as usize)? as u32 + 1; // 转为 1-based
|
||
let start_col = cell.unwrap_or(1);
|
||
|
||
let vals: Vec<&str> = value.split(split).collect();
|
||
|
||
for (i, val) in vals.iter().enumerate() {
|
||
let c = start_col + i as u32;
|
||
let cell_obj = sheet.get_cell_mut((c, target_row));
|
||
let val_trimmed = val.trim();
|
||
|
||
if !val_trimmed.is_empty() {
|
||
// 1. 尝试解析日期
|
||
if let Some((dt, excel_fmt)) = try_parse_datetime(val_trimmed) {
|
||
let serial = datetime_to_excel_serial(&dt);
|
||
cell_obj.set_value_number(serial);
|
||
cell_obj.get_style_mut().get_number_format_mut().set_format_code(excel_fmt);
|
||
} else {
|
||
// 2. 写入文本
|
||
cell_obj.set_value(val_trimmed);
|
||
}
|
||
}
|
||
}
|
||
|
||
println!("✓ 已更新第 {} 行 (样式未改变)", row);
|
||
Ok(())
|
||
}
|
||
|
||
fn delete_excel(
|
||
book: &mut Spreadsheet,
|
||
start: i32,
|
||
count: u32,
|
||
yes: bool,
|
||
sheet_name: &Option<String>,
|
||
) -> Result<()> {
|
||
let sheet = get_sheet_mut(book, sheet_name)?;
|
||
let (_, max_row) = sheet.get_highest_column_and_row();
|
||
|
||
// 解析起始行 (转为 1-based)
|
||
let start_del = if start < 0 {
|
||
// 负数:从后往前数
|
||
let pos = (max_row as i32 + start + 1).max(1) as u32;
|
||
pos
|
||
} else {
|
||
// 正数:直接使用
|
||
start.max(1) as u32
|
||
};
|
||
|
||
let end_del = (start_del + count - 1).min(max_row);
|
||
|
||
// 确认
|
||
if !yes {
|
||
eprintln!("⚠ 即将删除第 {} 至 {} 行(共 {} 行)", start_del, end_del, count);
|
||
eprint!("确认? (y/N): ");
|
||
std::io::stdout().flush()?;
|
||
let mut input = String::new();
|
||
std::io::stdin().read_line(&mut input)?;
|
||
if !input.trim().eq_ignore_ascii_case("y") {
|
||
println!("操作已取消");
|
||
return Ok(());
|
||
}
|
||
}
|
||
|
||
// 调用 umya 的删除方法
|
||
sheet.remove_row(&start_del, &count);
|
||
|
||
println!("✓ 已删除第 {} 至 {} 行(共 {} 行)", start_del, end_del, count);
|
||
Ok(())
|
||
}
|
||
|
||
fn style_excel(
|
||
book: &mut Spreadsheet,
|
||
rows: &[i32],
|
||
cols: &[u32],
|
||
row_height: Option<f64>,
|
||
col_width: Option<f64>,
|
||
wrap_text: bool,
|
||
sheet_name: &Option<String>,
|
||
) -> Result<()> {
|
||
let sheet = get_sheet_mut(book, sheet_name)?;
|
||
let (_, max_row) = sheet.get_highest_column_and_row();
|
||
let (max_col, _) = sheet.get_highest_column_and_row();
|
||
|
||
let target_rows: Vec<u32> = if rows.is_empty() {
|
||
(1..=max_row).collect()
|
||
} else {
|
||
rows.iter().filter_map(|&r| resolve_row(r, max_row as usize).ok()).map(|x| x as u32 + 1).collect()
|
||
};
|
||
|
||
let target_cols: Vec<u32> = if cols.is_empty() {
|
||
(1..=max_col).collect()
|
||
} else {
|
||
cols.to_vec()
|
||
};
|
||
|
||
for &r in &target_rows {
|
||
if let Some(h) = row_height {
|
||
let row = sheet.get_row_dimension_mut(&r);
|
||
row.set_height(h);
|
||
}
|
||
}
|
||
|
||
for &c in &target_cols {
|
||
if let Some(w) = col_width {
|
||
let col = sheet.get_column_dimension_by_number_mut(&c);
|
||
col.set_width(w);
|
||
}
|
||
}
|
||
|
||
if wrap_text {
|
||
use umya_spreadsheet::structs::HorizontalAlignmentValues;
|
||
for r in &target_rows {
|
||
for c in &target_cols {
|
||
let cell = sheet.get_cell_mut((*c, *r));
|
||
let mut align = cell.get_style().get_alignment().cloned().unwrap_or_default();
|
||
align.set_horizontal(HorizontalAlignmentValues::Left);
|
||
align.set_wrap_text(true);
|
||
cell.get_style_mut().set_alignment(align);
|
||
}
|
||
}
|
||
}
|
||
|
||
println!("✓ 样式应用成功");
|
||
Ok(())
|
||
} |