第一次提交
This commit is contained in:
137
src-tauri/src/excel_reader.rs
Normal file
137
src-tauri/src/excel_reader.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use anyhow::{Context, Result};
|
||||
use calamine::{open_workbook, Data, DataType, Reader, Xlsx};
|
||||
use chrono::{Duration, NaiveDate};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Student {
|
||||
pub id: Option<String>,
|
||||
pub year: Option<String>,
|
||||
pub grade: Option<String>,
|
||||
pub class: Option<String>,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AttendanceRecord {
|
||||
pub student: Student,
|
||||
// Date and attendance count (1.0 for present)
|
||||
pub attendance: Vec<(NaiveDate, f64)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ClassData {
|
||||
pub class_name: String,
|
||||
pub records: Vec<AttendanceRecord>,
|
||||
}
|
||||
|
||||
pub fn read_attendance_file<P: AsRef<Path>>(path: P) -> Result<Vec<ClassData>> {
|
||||
let mut workbook: Xlsx<_> = open_workbook(path).context("Failed to open Excel file")?;
|
||||
let mut classes = Vec::new();
|
||||
|
||||
for sheet_name in workbook.sheet_names().to_owned() {
|
||||
if let Ok(range) = workbook.worksheet_range(&sheet_name) {
|
||||
let mut records = Vec::new();
|
||||
let mut dates = Vec::new();
|
||||
let mut date_col_indices = Vec::new();
|
||||
|
||||
// Find header row (row 2, index 1)
|
||||
if range.height() < 2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse headers to find date columns
|
||||
// Assuming row 2 (index 1) contains headers and dates
|
||||
let header_row = range.rows().nth(1).unwrap();
|
||||
|
||||
for (col_idx, cell) in header_row.iter().enumerate() {
|
||||
// Dates in Excel are often floats or ints (serial dates)
|
||||
// Check if the cell value looks like a date (e.g., > 40000)
|
||||
if let Some(float_val) = cell.as_f64() {
|
||||
if float_val > 40000.0 && float_val < 50000.0 {
|
||||
// Convert serial date to NaiveDate
|
||||
// Excel base date is usually 1899-12-30
|
||||
let base_date = NaiveDate::from_ymd_opt(1899, 12, 30).unwrap();
|
||||
let days = float_val as i64;
|
||||
if let Some(date) = base_date.checked_add_signed(Duration::days(days)) {
|
||||
dates.push(date);
|
||||
date_col_indices.push(col_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dates.is_empty() {
|
||||
// Skip sheets without dates
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate over data rows (starting from row 4, index 3)
|
||||
// Row 1: Title, Row 2: Headers, Row 3: Weekdays, Row 4: Data
|
||||
for row in range.rows().skip(3) {
|
||||
// Check if row has a name (Column E, index 4)
|
||||
// If name is empty, skip
|
||||
let name_cell = &row[4];
|
||||
let name = match name_cell {
|
||||
Data::String(s) => s.trim().to_string(),
|
||||
_ => continue, // Skip if no name
|
||||
};
|
||||
|
||||
if name.is_empty() || name == "姓名" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let id = row[0].to_string();
|
||||
let year = row[1].to_string();
|
||||
let grade = row[2].to_string();
|
||||
let class_info = row[3].to_string();
|
||||
|
||||
let student = Student {
|
||||
id: if id.is_empty() { None } else { Some(id) },
|
||||
year: if year.is_empty() { None } else { Some(year) },
|
||||
grade: if grade.is_empty() { None } else { Some(grade) },
|
||||
class: if class_info.is_empty() { None } else { Some(class_info) },
|
||||
name,
|
||||
};
|
||||
|
||||
let mut attendance_entries = Vec::new();
|
||||
|
||||
for (i, &col_idx) in date_col_indices.iter().enumerate() {
|
||||
if col_idx < row.len() {
|
||||
let cell = &row[col_idx];
|
||||
// Check for "1" or numeric 1
|
||||
let is_present = match cell {
|
||||
Data::Int(v) => *v == 1,
|
||||
Data::Float(v) => (*v - 1.0).abs() < 1e-6,
|
||||
Data::String(s) => s.trim() == "1",
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if is_present {
|
||||
if i < dates.len() {
|
||||
attendance_entries.push((dates[i], 1.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !attendance_entries.is_empty() {
|
||||
records.push(AttendanceRecord {
|
||||
student,
|
||||
attendance: attendance_entries,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if !records.is_empty() {
|
||||
classes.push(ClassData {
|
||||
class_name: sheet_name.clone(),
|
||||
records,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(classes)
|
||||
}
|
||||
Reference in New Issue
Block a user