Initial commit: Add cutPic, dexcel, and zip tools

This commit is contained in:
macro
2026-04-25 00:01:40 +08:00
commit 8fb110adbd
15 changed files with 2704 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "dexcel"
version = "0.1.0"
edition = "2024"
[dependencies]
calamine = { version = "0.26", features = ["dates"] }
clap = { version = "4.5", features = ["derive"] }
rust_xlsxwriter = "0.80"
tabled = "0.16"
chrono = "0.4"
+514
View File
@@ -0,0 +1,514 @@
# dexcel - Excel 读写工具 📊
一个简单易用的命令行 Excel 处理工具,支持查询、统计、插入、更新和删除操作。
## ✨ 功能特性
### 核心操作
- 🔍 **query**: 灵活查询 Excel 数据,支持行范围选择和自定义分隔符
- 📊 **count**: 快速统计行数,适合脚本使用
- **insert**: 插入新行,支持指定位置或追加到末尾
- ✏️ **update**: 更新现有数据,支持部分列更新
-**delete**: 删除行或单元格,带安全确认机制
### 通用特性
- 📑 **多 Sheet 支持**: 所有操作都支持 `--sheet` 参数指定工作表
- 🔢 **负数索引**: 支持负数行号(-1 表示最后一行)
- 🆕 **自动创建**: insert 操作在文件不存在时自动创建
- ⚠️ **安全确认**: delete 操作需要用户确认
- 📤 **灵活输出**: query 支持自定义分隔符(默认 |)
## 📦 安装
### 从源码编译
```bash
cd dexcel
cargo build --release
```
编译后的可执行文件位于 `target/release/dexcel.exe` (Windows)
### 全局安装
```bash
cargo install --path . --force
```
安装后可直接使用 `dexcel` 命令。
## 🚀 使用方法
**基本格式:**
```bash
dexcel <文件路径> <操作> [参数]
```
**注意:文件路径是第一个位置参数,不需要 `--file` 标志!**
### 1. 查询数据 (query)
```bash
# 查看整个文件(默认显示最后20行)
dexcel data.xlsx query
# 查看前5行
dexcel data.xlsx query 5
# 查看最后3行
dexcel data.xlsx query -3
# 查看最后一行
dexcel data.xlsx query --last
# 从第10行开始查看
dexcel data.xlsx query --start 10
# 指定 Sheet 名称
dexcel data.xlsx query --sheet "Sales"
```
#### query 参数说明
- `[COUNT]`: 获取行数(正数从前,负数从后),默认0显示最后20行
- `--start <START>`: 开始行号(支持负数)
- `--last`: 获取最后一行
- `--sheet <SHEET>`: Sheet 名称(可选,默认第一个 Sheet)
### 2. 统计行数 (count)
```bash
# 统计总行数
dexcel data.xlsx count
# 统计指定 Sheet 的行数
dexcel data.xlsx count --sheet "Sales"
```
#### count 参数说明
- `--sheet <SHEET>`: Sheet 名称(可选,默认第一个 Sheet)
**输出**: 只输出一个数字,表示总行数,适合脚本中使用。
### 3. 插入数据 (insert)
```bash
# 创建新文件并插入表头
dexcel new-file.xlsx insert "姓名|年龄|城市"
# 追加数据到末尾
dexcel data.xlsx insert "张三|18|北京"
# 在指定位置插入(第2行)
dexcel data.xlsx insert "李四|20|上海" --row 2
# 使用不同分隔符
dexcel data.xlsx insert "王五,25,广州" --split ","
# 指定 Sheet
dexcel data.xlsx insert "数据" --sheet "Sheet2"
```
#### insert 参数说明
- `<VALUE>`: 要插入的值(用 split 分隔列)**必需**
- `--row <ROW>`: 插入位置(不指定则追加到末尾)
- `--sheet <SHEET>`: Sheet 名称(可选)
- `--split <SPLIT>`: 分隔符(默认 |
### 4. 更新数据 (update)
```bash
# 更新整行(从第1列开始)
dexcel data.xlsx update "赵六|22|深圳" --row 2
# 更新部分列(从第2列开始)
dexcel data.xlsx update "新年龄" --row 5 --cell 2
# 更新多列(从第3列开始)
dexcel data.xlsx update "新城市|新备注" --row 4 --cell 3
# 指定 Sheet
dexcel data.xlsx update "新值" --row 1 --sheet "Sheet2"
```
#### update 参数说明
- `<VALUE>`: 要更新的值(用 split 分隔列)**必需**
- `--row <ROW>`: 行号(必需,支持负数)
- `--cell <CELL>`: 起始列号(从1开始,默认1
- `--sheet <SHEET>`: Sheet 名称(可选)
- `--split <SPLIT>`: 分隔符(默认 |
### 5. 删除数据 (delete)
```bash
# 删除整行(带确认)
dexcel data.xlsx delete --row 5
# 删除最后一行
dexcel data.xlsx delete --row -1
# 删除单元格(第3行第2列)
dexcel data.xlsx delete --row 3 --cell 2
# 指定 Sheet
dexcel data.xlsx delete --row 1 --sheet "Sheet2"
```
#### delete 参数说明
- `[COUNT]`: 删除数量(默认1
- `--row <ROW>`: 行号(必需,支持负数)
- `--cell <CELL>`: 列号(可选,不指定则删除整行)
- `--sheet <SHEET>`: Sheet 名称(可选)
**注意**: delete 操作需要用户确认(输入 y 继续)
## 💡 使用示例
### 示例 1: 创建学生信息表
```bash
# 创建表头
dexcel students.xlsx insert "姓名|年龄|班级"
# 添加学生数据
dexcel students.xlsx insert "张三|18|一班"
dexcel students.xlsx insert "李四|19|二班"
dexcel students.xlsx insert "王五|20|三班"
# 查看所有数据
dexcel students.xlsx query
# 统计总行数
dexcel students.xlsx count
```
### 示例 2: 数据查询和分析
```bash
# 查看前5行
dexcel data.xlsx query 5
# 查看最后10行
dexcel data.xlsx query -10
# 从第50行开始查看
dexcel data.xlsx query --start 50
# 导出到文件
dexcel data.xlsx query > output.txt
# 使用逗号分隔
dexcel data.xlsx query --split ","
```
### 示例 3: 数据更新
```bash
# 更新第2行的所有数据
dexcel data.xlsx update "新姓名|新年龄|新城市" --row 2
# 只更新第2行的第2列
dexcel data.xlsx update "新年龄" --row 2 --cell 2
# 批量更新(脚本)
for i in 2 3 4 5; do
dexcel data.xlsx update "已审核" --row $i --cell 4
done
```
### 示例 4: 数据清理
```bash
# 删除错误数据行
dexcel data.xlsx delete --row 10
# 删除最后一行
dexcel data.xlsx delete --row -1
# 删除特定单元格
dexcel data.xlsx delete --row 5 --cell 3
```
### 示例 5: 多 Sheet 操作
```bash
# 在不同 Sheet 中操作
dexcel data.xlsx insert "数据1" --sheet "Sheet1"
dexcel data.xlsx insert "数据2" --sheet "Sheet2"
# 查询指定 Sheet
dexcel data.xlsx query --sheet "Sales"
# 统计指定 Sheet 的行数
dexcel data.xlsx count --sheet "Sales"
```
## 📊 输出格式
### query 输出(表格格式)
```
┌──────┬──────┬──────┐
│ 姓名 │ 年龄 │ 城市 │
├──────┼──────┼──────┤
│ 张三 │ 25 │ 北京 │
├──────┼──────┼──────┤
│ 李四 │ 30 │ 上海 │
└──────┴──────┴──────┘
共输出 2 行数据,3 列
```
**特点**:
- ✅ 使用美观的表格格式(基于 tabled 库)
- ✅ 自动对齐列宽
- ✅ 清晰的分隔线和边框
- ✅ 显示行数和列数统计
- ✅ 易于阅读和查看
### count 输出
```
100
```
**特点**:
- 只输出一个数字
- 适合脚本中使用
- 可与其他命令组合
### 操作反馈
**insert 成功**:
```
✓ 操作成功:已创建新文件并插入一行数据
文件: data.xlsx, Sheet: Sheet1
```
**update 成功**:
```
✓ 操作成功:已更新第 5 行,从第 1 列开始
文件: data.xlsx, Sheet: Sheet1
```
**delete 成功**:
```
✓ 操作成功:已删除第 5 行
剩余行数: 9
文件: data.xlsx, Sheet: Sheet1
```
## ⚙️ 技术细节
### 依赖库
- **calamine**: Excel 文件读取库,支持 .xlsx 格式
- **rust_xlsxwriter**: Excel 文件写入库,功能强大
- **clap**: 命令行参数解析库
- **chrono**: 日期时间处理库
### 工作原理
#### 查询流程
1. 打开 Excel 文件
2. 选择指定的 Sheet(默认第一个)
3. 根据参数计算读取范围
4. 遍历单元格并提取数据
5. 以指定分隔符格式输出
#### 写入流程
1. 检查文件是否存在(insert 可自动创建)
2. 读取现有数据到新工作簿
3. 执行插入/更新/删除操作
4. 保存文件
### 数据类型支持
**读取时支持:**
- 字符串 (String)
- 整数 (Int)
- 浮点数 (Float)
- 布尔值 (Bool)
- 日期时间 (DateTime) - 自动识别并格式化
- 空值 (Empty)
- 错误值 (Error)
**写入时:**
- 所有数据都作为字符串写入
## ⚠️ 注意事项
1. **索引从 1 开始**: 行和列的索引都是从 1 开始,不是 0
2. **负数索引**: `-1` = 最后一行/列,`-2` = 倒数第二
3. **文件自动创建**: insert 操作在文件不存在时自动创建
4. **分隔符默认**: 默认使用 `|` 分隔列
5. **文件格式**: 仅支持 `.xlsx`,不支持 `.xls`
6. **删除需确认**: delete 操作必须用户确认才能执行
7. **日期自动识别**: 自动识别并格式化日期单元格
8. **不可撤销**: delete 操作不可撤销,请谨慎操作!
### 限制
**读取限制:**
- 公式: 不计算公式,只读取原始值
- 样式: 不读取单元格样式(颜色、字体等)
- 图表: 不支持读取图表
- 宏: 不支持 VBA 宏
- 大文件: > 10MB 的文件可能较慢
**写入限制:**
- 无样式: 不能设置颜色、字体等样式
- 无公式: 不能写入公式
- 覆盖警告: update 会覆盖目标单元格的原有内容
- insert 下移: insert 会将原有数据下移
## 🔧 常见问题
### Q1: 如何查看文件有多少行?
```bash
dexcel data.xlsx count
```
### Q2: 如何追加多行数据?
多次调用 insert 命令:
```bash
dexcel data.xlsx insert "数据1|数据2"
dexcel data.xlsx insert "数据3|数据4"
```
### Q3: 负数参数怎么用?
```bash
dexcel data.xlsx query -5 # 最后5行
dexcel data.xlsx delete --row -1 # 删除最后一行
```
### Q4: 如何处理中文内容?
直接传入,完全支持 UTF-8
```bash
dexcel data.xlsx insert "张三|18|北京"
```
### Q5: 如何在脚本中使用 count
```bash
# 获取行数并存储到变量
$lines = dexcel data.xlsx count
# 条件判断
if ((dexcel data.xlsx count) -gt 100) {
Write-Host "文件超过100行"
}
```
### Q6: 删除操作能撤销吗?
不能撤销,请谨慎操作!建议先备份文件。
## 🎯 应用场景
### 1. 数据预览
快速查看大型 Excel 文件的部分内容:
```bash
dexcel large-file.xlsx query 5
```
### 2. 数据提取
提取特定数据并导出:
```bash
dexcel data.xlsx query -10 > last-rows.txt
```
### 3. 报告生成
创建报告模板:
```bash
dexcel report.xlsx insert "月度报告||||"
dexcel report.xlsx insert "日期|销售额|利润|备注"
```
### 4. 数据更新
批量更新某列数据:
```bash
dexcel data.xlsx update "新值" --row 5 --cell 2
dexcel data.xlsx update "新值" --row 6 --cell 2
```
### 5. 数据清理
删除错误数据:
```bash
dexcel data.xlsx delete --row 10
```
### 6. 自动化脚本
PowerShell 批量导入:
```powershell
$students = @(
@("张三", "18", "一班"),
@("李四", "19", "二班")
)
foreach ($student in $students) {
dexcel students.xlsx insert "$($student[0])|$($student[1])|$($student[2])"
}
```
## 📝 批处理脚本示例
### Windows 批量导入 (import.bat)
```batch
@echo off
echo 正在导入数据...
dexcel data.xlsx insert "ID|名称|数量"
dexcel data.xlsx insert "001|产品A|100"
dexcel data.xlsx insert "002|产品B|200"
dexcel data.xlsx insert "003|产品C|300"
echo 导入完成!
dexcel data.xlsx query
pause
```
### PowerShell 数据处理 (process.ps1)
```powershell
# 读取并处理数据
$output = dexcel data.xlsx query 100
# 过滤包含关键词的行
$filtered = $output | Select-String "关键词"
# 保存到文件
$filtered | Out-File -FilePath filtered.txt -Encoding UTF8
Write-Host "处理完成!" -ForegroundColor Green
```
## 🤝 贡献
欢迎提交 Issue 和 Pull Request
可能的改进方向:
- 支持更多数据类型(公式等)
- 支持单元格样式(颜色、字体等)
- 支持批量写入优化
- 支持复制 Sheet
- 添加数据验证功能
- 支持更多输出格式(CSV, JSON 等)
## 📄 许可证
本项目采用 MIT 许可证。
## 🙏 致谢
感谢以下开源项目:
- [calamine](https://github.com/tafia/calamine) - 优秀的 Excel 读取库
- [rust_xlsxwriter](https://github.com/jmcnamara/rust_xlsxwriter) - 强大的 Excel 写入库
- [clap](https://github.com/clap-rs/clap) - 优秀的命令行参数解析库
- [chrono](https://github.com/chronotope/chrono) - 日期时间处理库
---
**Made with ❤️ using Rust**
+48
View File
@@ -0,0 +1,48 @@
@echo off
set DEXCEL=%~dp0target\release\dexcel.exe
echo ====================================
echo dexcel 工具使用示例
echo ====================================
echo.
echo 1. 创建测试 Excel 文件
echo -------------------
%DEXCEL% write -f example.xlsx -x 1 -y 1 -v "姓名"
%DEXCEL% write -f example.xlsx -x 1 -y 2 -v "年龄"
%DEXCEL% write -f example.xlsx -x 1 -y 3 -v "城市"
%DEXCEL% write -f example.xlsx -x 2 -y 1 -v "张三"
%DEXCEL% write -f example.xlsx -x 2 -y 2 -v "25"
%DEXCEL% write -f example.xlsx -x 2 -y 3 -v "北京"
%DEXCEL% write -f example.xlsx -x 3 -y 1 -v "李四"
%DEXCEL% write -f example.xlsx -x 3 -y 2 -v "30"
%DEXCEL% write -f example.xlsx -x 3 -y 3 -v "上海"
echo.
echo 2. 读取整个文件
echo -------------------
%DEXCEL% read -f example.xlsx
echo.
echo 3. 读取第2行开始的数据(2行)
echo -------------------
%DEXCEL% read -f example.xlsx -r 2 -n 2
echo.
echo 4. 只读取姓名列(第1列)
echo -------------------
%DEXCEL% read -f example.xlsx -c 1 -m 1
echo.
echo 5. 读取最后一列(城市列)
echo -------------------
%DEXCEL% read -f example.xlsx -c -1 -m 1
echo.
echo 6. 读取特定单元格(第2行,第2列)
echo -------------------
%DEXCEL% read -f example.xlsx -r 2 -n 1 -c 2 -m 1
echo.
echo ====================================
echo 示例完成!
echo ====================================
+706
View File
@@ -0,0 +1,706 @@
use calamine::{open_workbook, Data, DataType, Reader, Xlsx};
use chrono::{Datelike, Timelike};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "dexcel")]
#[command(about = "Excel 读写工具", long_about = None)]
struct Cli {
/// Excel 文件路径
file: PathBuf,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// 查询数据
Query {
/// 获取行数(正数从前获取,负数从后获取)
#[arg(default_value_t = 0, allow_hyphen_values = true)]
count: i32,
/// 开始行号(从1开始,负数表示倒数)
#[arg(long, allow_hyphen_values = true)]
start: Option<i32>,
/// 获取最后一行
#[arg(long)]
last: bool,
/// Sheet 名称(可选,默认为第一个sheet)
#[arg(long)]
sheet: Option<String>,
},
/// 统计行数
Count {
/// Sheet 名称(可选,默认为第一个sheet)
#[arg(long)]
sheet: Option<String>,
},
/// 插入行
Insert {
/// 要插入的值(使用 split 分隔列)
value: String,
/// 插入位置(行号,不指定则追加到末尾)
#[arg(long, allow_hyphen_values = true)]
row: Option<i32>,
/// Sheet 名称(可选,默认为第一个sheet)
#[arg(long)]
sheet: Option<String>,
/// 分割符号(默认 |
#[arg(long, default_value = "|")]
split: String,
},
/// 更新数据
Update {
/// 要更新的值(使用 split 分隔列)
value: String,
/// 行号(必需)
#[arg(long, allow_hyphen_values = true)]
row: i32,
/// 起始列号(从1开始,不指定则从第1列开始)
#[arg(long)]
cell: Option<u32>,
/// Sheet 名称(可选,默认为第一个sheet)
#[arg(long)]
sheet: Option<String>,
/// 分割符号(默认 |
#[arg(long, default_value = "|")]
split: String,
},
/// 删除数据
Delete {
/// 删除数量(单元格数或行数)
#[arg(default_value_t = 1)]
count: u32,
/// 行号(必需)
#[arg(long, allow_hyphen_values = true)]
row: i32,
/// 列号(可选,不指定则删除整行)
#[arg(long)]
cell: Option<u32>,
/// Sheet 名称(可选,默认为第一个sheet)
#[arg(long)]
sheet: Option<String>,
},
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Query {
count,
start,
last,
sheet,
} => {
if let Err(e) = query_excel(&cli.file, count, start, last, &sheet) {
eprintln!("错误: {}", e);
std::process::exit(1);
}
}
Commands::Count { sheet } => {
if let Err(e) = count_excel(&cli.file, &sheet) {
eprintln!("错误: {}", e);
std::process::exit(1);
}
}
Commands::Insert {
value,
row,
sheet,
split,
} => {
if let Err(e) = insert_excel(&cli.file, &value, row, &sheet, &split) {
eprintln!("错误: {}", e);
std::process::exit(1);
}
}
Commands::Update {
value,
row,
cell,
sheet,
split,
} => {
if let Err(e) = update_excel(&cli.file, &value, row, cell, &sheet, &split) {
eprintln!("错误: {}", e);
std::process::exit(1);
}
}
Commands::Delete {
count,
row,
cell,
sheet,
} => {
if let Err(e) = delete_excel(&cli.file, count, row, cell, &sheet) {
eprintln!("错误: {}", e);
std::process::exit(1);
}
}
}
}
// 格式化单元格值为字符串
fn format_cell(cell: &Data) -> String {
match cell {
Data::Empty => String::new(),
Data::Int(v) => v.to_string(),
Data::Float(v) => {
if *v > 1.0 && *v < 2958466.0 && (*v - v.floor()).abs() < 0.0001 {
let temp_data = Data::Float(*v);
if let Some(dt) = temp_data.as_datetime() {
let year = dt.year();
if year >= 1900 && year <= 2100 {
return dt.format("%Y-%m-%d").to_string();
}
}
}
v.to_string()
}
Data::String(s) => s.clone(),
Data::Bool(b) => b.to_string(),
Data::DateTime(d) => {
if let Some(dt) = d.as_datetime() {
if dt.hour() == 0 && dt.minute() == 0 && dt.second() == 0 {
dt.format("%Y-%m-%d").to_string()
} else {
dt.format("%Y-%m-%d %H:%M:%S").to_string()
}
} else {
d.to_string()
}
}
Data::DateTimeIso(s) => s.clone(),
Data::DurationIso(s) => s.clone(),
Data::Error(e) => format!("#{}", e),
_ => String::new(),
}
}
// 统计行数(count 命令)
fn count_excel(
file: &PathBuf,
sheet_name: &Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut workbook: Xlsx<_> = open_workbook(file)?;
let sheet = match sheet_name {
Some(name) => name.clone(),
None => {
let sheets = workbook.sheet_names();
if sheets.is_empty() {
return Err("Excel 文件中没有 sheet".into());
}
sheets[0].clone()
}
};
let range = workbook
.worksheet_range(&sheet)
.map_err(|e| format!("找不到 sheet: {}, 错误: {}", sheet, e))?;
let total_rows = range.height();
println!("{}", total_rows);
Ok(())
}
// 查询数据(query 命令)
fn query_excel(
file: &PathBuf,
count: i32,
start: Option<i32>,
last: bool,
sheet_name: &Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut workbook: Xlsx<_> = open_workbook(file)?;
let sheet = match sheet_name {
Some(name) => name.clone(),
None => {
let sheets = workbook.sheet_names();
if sheets.is_empty() {
return Err("Excel 文件中没有 sheet".into());
}
sheets[0].clone()
}
};
let range = workbook
.worksheet_range(&sheet)
.map_err(|e| format!("找不到 sheet: {}, 错误: {}", sheet, e))?;
let total_rows = range.height();
let _total_cols = range.width();
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 {
// 使用 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 {
let abs_count = (-count) as usize;
let end = (actual_start + abs_count).min(total_rows);
(actual_start, end)
} else {
// count 为 0,显示从 start 到末尾(最多20行)
let max_display = 20;
let end = (actual_start + max_display).min(total_rows);
if total_rows - actual_start > max_display {
eprintln!("提示: 从第 {} 行开始共有 {} 行数据,仅显示 {}",
actual_start + 1, total_rows - actual_start, max_display);
}
(actual_start, end)
}
} else if count > 0 {
(0, count as usize)
} else if count < 0 {
let abs_count = (-count) as usize;
let start = if abs_count >= total_rows { 0 } else { total_rows - abs_count };
(start, total_rows)
} else {
// 默认显示最后20行
let max_display = 20;
if total_rows > max_display {
eprintln!("提示: 共有 {} 行数据,仅显示最后 {}", total_rows, max_display);
eprintln!("如需查看更多数据,请使用 dexcel query <行数> 或 --last\n");
(total_rows - max_display, total_rows)
} else {
(0, total_rows)
}
};
let end_row = end_row.min(total_rows);
// 收集所有数据
let mut data: Vec<Vec<String>> = Vec::new();
for r in start_row..end_row {
let mut row_values = Vec::new();
for c in 0..range.width() {
let cell = range.get((r, c));
if let Some(cell_data) = cell {
row_values.push(format_cell(cell_data));
} else {
row_values.push(String::new());
}
}
data.push(row_values);
}
if data.is_empty() {
println!("没有数据");
return Ok(());
}
// 使用 tabled 输出表格
let output_count = data.len();
let col_count = data[0].len();
// 使用 Builder API 创建动态表格
use tabled::builder::Builder;
let mut builder = Builder::default();
// 添加所有行
for row in &data {
builder.push_record(row.iter().map(|s| s.as_str()));
}
let mut table = builder.build();
// 设置样式
table.with(tabled::settings::Style::modern());
println!("{}", table);
eprintln!("\n共输出 {} 行数据,{}", output_count, col_count);
Ok(())
}
// 插入行(insert 命令)
fn insert_excel(
file: &PathBuf,
value: &str,
row: Option<i32>,
sheet_name: &Option<String>,
split: &str,
) -> Result<(), Box<dyn std::error::Error>> {
use rust_xlsxwriter::*;
let sheet = match sheet_name {
Some(name) => name.clone(),
None => "Sheet1".to_string(),
};
// 如果文件不存在,创建新文件
if !file.exists() {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();
worksheet.set_name(&sheet)?;
let values: Vec<&str> = value.split(split).collect();
for (col_idx, val) in values.iter().enumerate() {
let trimmed = val.trim();
if !trimmed.is_empty() {
worksheet.write_string(0, col_idx as u16, trimmed)?;
}
}
workbook.save(file.as_path())?;
println!("✓ 操作成功:已创建新文件并插入一行数据");
eprintln!(" 文件: {}, Sheet: {}", file.display(), sheet);
return Ok(());
}
let mut workbook: Xlsx<_> = open_workbook(file.as_path())?;
let range = workbook
.worksheet_range(&sheet)
.map_err(|e| format!("找不到 sheet: {}, 错误: {}", sheet, e))?;
let total_rows = range.height();
let total_cols = range.width();
// 确定插入位置
let insert_row = match row {
Some(r) => {
if r < 0 {
let pos = total_rows as i32 + r + 1;
if pos < 0 || pos > total_rows as i32 {
return Err(format!("行号无效: {}", r).into());
}
pos as usize
} else {
let pos = r as usize - 1;
if pos > total_rows {
return Err(format!("行号超出范围: {} (总行数: {})", r, total_rows).into());
}
pos
}
}
None => total_rows, // 默认追加到末尾
};
// 创建新工作簿
let mut new_workbook = Workbook::new();
let new_worksheet = new_workbook.add_worksheet();
new_worksheet.set_name(&sheet)?;
let values: Vec<&str> = value.split(split).collect();
let mut new_row = 0u32;
// 复制插入行之前的数据
for r in 0..insert_row {
for c in 0..range.width() {
if let Some(cell_data) = range.get((r, c)) {
write_cell(new_worksheet, new_row, c as u16, cell_data)?;
}
}
new_row += 1;
}
// 写入新行
for (col_idx, val) in values.iter().enumerate() {
let trimmed = val.trim();
if !trimmed.is_empty() {
new_worksheet.write_string(new_row, col_idx as u16, trimmed)?;
}
}
new_row += 1;
// 复制插入行之后的数据
for r in insert_row..total_rows {
for c in 0..range.width() {
if let Some(cell_data) = range.get((r, c)) {
write_cell(new_worksheet, new_row, c as u16, cell_data)?;
}
}
new_row += 1;
}
// 保存文件
new_workbook.save(file.as_path())?;
println!("✓ 操作成功:已在第 {} 行插入一行数据", insert_row + 1);
eprintln!(" 文件: {}, Sheet: {}, 总行数: {}", file.display(), sheet, total_rows + 1);
Ok(())
}
// 更新数据(update 命令)
fn update_excel(
file: &PathBuf,
value: &str,
row: i32,
cell: Option<u32>,
sheet_name: &Option<String>,
split: &str,
) -> Result<(), Box<dyn std::error::Error>> {
use rust_xlsxwriter::*;
if !file.exists() {
return Err(format!("文件不存在: {}", file.display()).into());
}
let mut workbook: Xlsx<_> = open_workbook(file.as_path())?;
let sheet = match sheet_name {
Some(name) => name.clone(),
None => {
let sheets = workbook.sheet_names();
if sheets.is_empty() {
return Err("Excel 文件中没有 sheet".into());
}
sheets[0].clone()
}
};
let range = workbook
.worksheet_range(&sheet)
.map_err(|e| format!("找不到 sheet: {}, 错误: {}", sheet, e))?;
let total_rows = range.height();
let total_cols = range.width();
// 确定行号
let target_row = if row < 0 {
let pos = total_rows as i32 + row;
if pos < 0 || pos >= total_rows as i32 {
return Err(format!("行号无效: {}", row).into());
}
pos as usize
} else {
let pos = row as usize - 1;
if pos >= total_rows {
return Err(format!("行号超出范围: {} (总行数: {})", row, total_rows).into());
}
pos
};
// 确定起始列
let start_col = cell.map(|c| (c - 1) as usize).unwrap_or(0);
// 创建新工作簿并复制所有数据
let mut new_workbook = Workbook::new();
let new_worksheet = new_workbook.add_worksheet();
new_worksheet.set_name(&sheet)?;
let values: Vec<&str> = value.split(split).collect();
for r in 0..total_rows {
for c in 0..range.width() {
if r == target_row && c >= start_col {
// 更新这一行的指定列
let col_offset = c - start_col;
if col_offset < values.len() {
let trimmed = values[col_offset].trim();
if !trimmed.is_empty() {
new_worksheet.write_string(r as u32, c as u16, trimmed)?;
}
} else {
// 超出提供的值,保持原样
if let Some(cell_data) = range.get((r, c)) {
write_cell(new_worksheet, r as u32, c as u16, cell_data)?;
}
}
} else {
// 复制原有数据
if let Some(cell_data) = range.get((r, c)) {
write_cell(new_worksheet, r as u32, c as u16, cell_data)?;
}
}
}
}
// 保存文件
new_workbook.save(file.as_path())?;
println!("✓ 操作成功:已更新第 {} 行,从第 {} 列开始", target_row + 1, start_col + 1);
eprintln!(" 文件: {}, Sheet: {}", file.display(), sheet);
Ok(())
}
// 删除数据(delete 命令)
fn delete_excel(
file: &PathBuf,
count: u32,
row: i32,
cell: Option<u32>,
sheet_name: &Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
use rust_xlsxwriter::*;
if !file.exists() {
return Err(format!("文件不存在: {}", file.display()).into());
}
let mut workbook: Xlsx<_> = open_workbook(file.as_path())?;
let sheet = match sheet_name {
Some(name) => name.clone(),
None => {
let sheets = workbook.sheet_names();
if sheets.is_empty() {
return Err("Excel 文件中没有 sheet".into());
}
sheets[0].clone()
}
};
let range = workbook
.worksheet_range(&sheet)
.map_err(|e| format!("找不到 sheet: {}, 错误: {}", sheet, e))?;
let total_rows = range.height();
let total_cols = range.width();
// 确定行号
let target_row = if row < 0 {
let pos = total_rows as i32 + row;
if pos < 0 || pos >= total_rows as i32 {
return Err(format!("行号无效: {}", row).into());
}
pos as usize
} else {
let pos = row as usize - 1;
if pos >= total_rows {
return Err(format!("行号超出范围: {} (总行数: {})", row, total_rows).into());
}
pos
};
// 显示确认信息
if let Some(c) = cell {
eprintln!("⚠ 警告:即将删除单元格");
eprintln!(" 文件: {}", file.display());
eprintln!(" Sheet: {}", sheet);
eprintln!(" 位置: 第 {} 行, 第 {}", target_row + 1, c);
} else {
eprintln!("⚠ 警告:即将删除以下数据:");
eprintln!(" 文件: {}", file.display());
eprintln!(" Sheet: {}", sheet);
eprintln!(" 删除行: 第 {} 行(共 {} 行)", target_row + 1, total_rows);
// 显示要删除的行的内容
eprint!(" 内容: ");
for c in 0..range.width() {
if let Some(cell_data) = range.get((target_row, c)) {
eprint!("{}", format_cell(cell_data));
if c < range.width() - 1 {
eprint!(" | ");
}
}
}
eprintln!();
}
eprintln!();
// 请求用户确认
eprint!("是否继续?(y/N): ");
use std::io::{self, Write};
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim().to_lowercase();
if input != "y" && input != "yes" {
println!("✗ 操作已取消");
return Ok(());
}
// 创建新工作簿
let mut new_workbook = Workbook::new();
let new_worksheet = new_workbook.add_worksheet();
new_worksheet.set_name(&sheet)?;
if let Some(c) = cell {
// 删除单元格:将该单元格及其右侧的单元格左移
let target_col = (c - 1) as usize;
let mut new_row = 0u32;
for r in 0..total_rows {
let mut new_col = 0u16;
for col in 0..total_cols {
if r == target_row && col == target_col {
continue; // 跳过要删除的单元格
}
if let Some(cell_data) = range.get((r, col)) {
write_cell(new_worksheet, new_row, new_col, cell_data)?;
new_col += 1;
}
}
new_row += 1;
}
println!("✓ 操作成功:已删除第 {} 行第 {} 列的单元格", target_row + 1, c);
} else {
// 删除整行
let mut new_row = 0u32;
for r in 0..total_rows {
if r == target_row {
continue; // 跳过要删除的行
}
for c in 0..range.width() {
if let Some(cell_data) = range.get((r, c)) {
write_cell(new_worksheet, new_row, c as u16, cell_data)?;
}
}
new_row += 1;
}
println!("✓ 操作成功:已删除第 {}", target_row + 1);
eprintln!(" 剩余行数: {}", total_rows - 1);
}
// 保存文件
new_workbook.save(file.as_path())?;
eprintln!(" 文件: {}, Sheet: {}", file.display(), sheet);
Ok(())
}
// 辅助函数:写入单元格
fn write_cell(
worksheet: &mut rust_xlsxwriter::Worksheet,
row: u32,
col: u16,
data: &Data,
) -> Result<(), rust_xlsxwriter::XlsxError> {
match data {
Data::String(s) => worksheet.write_string(row, col, s.as_str()).map(|_| ()),
Data::Int(v) => worksheet.write_number(row, col, *v as f64).map(|_| ()),
Data::Float(v) => worksheet.write_number(row, col, *v).map(|_| ()),
Data::Bool(b) => worksheet.write_boolean(row, col, *b).map(|_| ()),
_ => Ok(()),
}
}