feat : update info

This commit is contained in:
2025-12-03 09:53:10 +08:00
parent e09f1f38e6
commit b088d9c8ee
6 changed files with 178 additions and 68 deletions

View File

@@ -7,31 +7,67 @@ pub fn write_report<P: AsRef<Path>>(path: P, reports: Vec<ClassReport>) -> Resul
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();
// Define Colors
let header_bg_color = rust_xlsxwriter::Color::RGB(0x4472C4); // Blue for main headers
let subheader_bg_color = rust_xlsxwriter::Color::RGB(0x4874CB); // Slightly different blue for subheaders
let text_color = rust_xlsxwriter::Color::White; // White text on blue background
let border_color = rust_xlsxwriter::Color::Black;
// Formats
let header_format = Format::new().set_bold().set_align(rust_xlsxwriter::FormatAlign::Center);
let center_format = Format::new().set_align(rust_xlsxwriter::FormatAlign::Center);
// 1. Main Header Format (Class Name, Row 1 Headers)
// Songti, 16pt, Bold, Blue Fill, White Text, Center, Thin Borders
let main_header_format = Format::new()
.set_font_name("宋体")
.set_font_size(16)
.set_bold()
.set_background_color(header_bg_color)
.set_font_color(text_color)
.set_align(rust_xlsxwriter::FormatAlign::Center)
.set_align(rust_xlsxwriter::FormatAlign::VerticalCenter)
.set_border(rust_xlsxwriter::FormatBorder::Thin)
.set_border_color(border_color);
// 2. Sub Header Format (Row 2 Headers: 次数, 单价, 费用合计)
// Songti, 11pt, Bold, Blue Fill, White Text, Center, Thin Borders
let sub_header_format = Format::new()
.set_font_name("宋体")
.set_font_size(11)
.set_bold()
.set_background_color(subheader_bg_color)
.set_font_color(text_color)
.set_align(rust_xlsxwriter::FormatAlign::Center)
.set_align(rust_xlsxwriter::FormatAlign::VerticalCenter)
.set_border(rust_xlsxwriter::FormatBorder::Thin)
.set_border_color(border_color);
// 3. Data Cell Format
// Songti, 10pt, Center, Thin Borders
let data_format = Format::new()
.set_font_name("宋体")
.set_font_size(10)
.set_align(rust_xlsxwriter::FormatAlign::Center)
.set_align(rust_xlsxwriter::FormatAlign::VerticalCenter)
.set_border(rust_xlsxwriter::FormatBorder::Thin)
.set_border_color(border_color);
let mut current_row = 0;
for report in reports {
// Write Class Name
worksheet.write_string(current_row, 0, &report.class_name)?;
// Write Class Name (Merged across columns A-E?)
// Actually, looking at res.xlsx, "思维B" is in A147.
// Let's merge A to E for the class name to look nice, or just put in A.
// The user sample shows it in A.
worksheet.write_string_with_format(current_row, 0, &report.class_name, &main_header_format)?;
// Apply empty format to B-E to show borders/bg if we want, but let's stick to simple first.
current_row += 1;
// Write Headers
let headers = ["序号", "入学年份", "年级", "班级", "姓名"];
for (i, header) in headers.iter().enumerate() {
worksheet.write_string_with_format(current_row, i as u16, *header, &header_format)?;
worksheet.write_string_with_format(current_row, i as u16, *header, &main_header_format)?;
}
// Determine all unique months across all students in this class to create columns
// Actually, the requirement implies we list months for each student?
// Looking at res.xlsx:
// Row 148: ..., 9月陈南岚老师, None, None, 10月陈南岚老师, ...
// Row 149: ..., 次数, 单价, 费用合计, 次数, 单价, 费用合计
// It seems the columns are dynamic based on months.
// We need to find all months present in this class and create columns for them.
// Collect all months
let mut all_months = std::collections::BTreeSet::new();
for student in &report.students {
@@ -46,9 +82,8 @@ pub fn write_report<P: AsRef<Path>>(path: P, reports: Vec<ClassReport>) -> Resul
for month in &all_months {
// Write Month Header (merged 3 cells)
// We don't know the teacher name, so just use "X月"
let month_str = format!("{}", month);
worksheet.merge_range(current_row, col_idx, current_row, col_idx + 2, &month_str, &header_format)?;
worksheet.merge_range(current_row, col_idx, current_row, col_idx + 2, &month_str, &main_header_format)?;
month_col_map.insert(*month, col_idx);
col_idx += 3;
@@ -57,16 +92,17 @@ pub fn write_report<P: AsRef<Path>>(path: P, reports: Vec<ClassReport>) -> Resul
current_row += 1;
// Write Sub-headers (次数, 单价, 费用合计)
for _ in &all_months {
// We need to know the column index for this month
// But we are iterating linearly.
}
// Actually, let's iterate columns again
// Columns A-E are empty in this row in the sample?
// Sample Row 149: [None, None, None, None, None, '次数', ...]
// So we leave A-E empty or apply a default format?
// Let's apply data format to keep borders consistent if needed, or just leave blank.
// Sample shows blank.
let mut sub_col_idx = 5;
for _ in &all_months {
worksheet.write_string_with_format(current_row, sub_col_idx, "次数", &center_format)?;
worksheet.write_string_with_format(current_row, sub_col_idx + 1, "单价", &center_format)?;
worksheet.write_string_with_format(current_row, sub_col_idx + 2, "费用合计", &center_format)?;
worksheet.write_string_with_format(current_row, sub_col_idx, "次数", &sub_header_format)?;
worksheet.write_string_with_format(current_row, sub_col_idx + 1, "单价", &sub_header_format)?;
worksheet.write_string_with_format(current_row, sub_col_idx + 2, "费用合计", &sub_header_format)?;
sub_col_idx += 3;
}
@@ -74,36 +110,67 @@ pub fn write_report<P: AsRef<Path>>(path: P, reports: Vec<ClassReport>) -> Resul
// Write Student Data
for (i, student) in report.students.iter().enumerate() {
worksheet.write_number_with_format(current_row, 0, (i + 1) as f64, &center_format)?;
worksheet.write_number_with_format(current_row, 0, (i + 1) as f64, &data_format)?;
if let Some(year) = &student.student.year {
worksheet.write_string_with_format(current_row, 1, year, &center_format)?;
worksheet.write_string_with_format(current_row, 1, year, &data_format)?;
} else {
worksheet.write_blank(current_row, 1, &data_format)?;
}
if let Some(grade) = &student.student.grade {
worksheet.write_string_with_format(current_row, 2, grade, &center_format)?;
worksheet.write_string_with_format(current_row, 2, grade, &data_format)?;
} else {
worksheet.write_blank(current_row, 2, &data_format)?;
}
if let Some(class_info) = &student.student.class {
worksheet.write_string_with_format(current_row, 3, class_info, &center_format)?;
worksheet.write_string_with_format(current_row, 3, class_info, &data_format)?;
} else {
worksheet.write_blank(current_row, 3, &data_format)?;
}
worksheet.write_string_with_format(current_row, 4, &student.student.name, &center_format)?;
worksheet.write_string_with_format(current_row, 4, &student.student.name, &data_format)?;
for record in &student.monthly_records {
if let Some(&start_col) = month_col_map.get(&record.month) {
worksheet.write_number_with_format(current_row, start_col, record.count as f64, &center_format)?;
worksheet.write_number_with_format(current_row, start_col + 1, record.price, &center_format)?;
worksheet.write_number_with_format(current_row, start_col, record.count as f64, &data_format)?;
worksheet.write_number_with_format(current_row, start_col + 1, record.price, &data_format)?;
// Formula: =PRODUCT(次数:单价)
// Column indices are 0-based.
// rust_xlsxwriter uses (row, col)
let count_cell = rust_xlsxwriter::utility::row_col_to_cell(current_row, start_col);
let price_cell = rust_xlsxwriter::utility::row_col_to_cell(current_row, start_col + 1);
let formula = format!("=PRODUCT({}:{})", count_cell, price_cell);
worksheet.write_formula_with_format(current_row, start_col + 2, formula.as_str(), &center_format)?;
worksheet.write_formula_with_format(current_row, start_col + 2, formula.as_str(), &data_format)?;
}
}
current_row += 1;
}
// Add Total Row
// Sample Row 162: [None, ..., =SUM(...)]
// We need to sum the columns.
// Let's add a "Total" row.
// Columns F, H, I, K, etc. (Count and Total Cost)
// We need to iterate through all month columns again
for month in &all_months {
if let Some(&start_col) = month_col_map.get(month) {
// Sum Count (start_col)
let start_cell = rust_xlsxwriter::utility::row_col_to_cell(current_row - report.students.len() as u32, start_col);
let end_cell = rust_xlsxwriter::utility::row_col_to_cell(current_row - 1, start_col);
let formula = format!("=SUM({}:{})", start_cell, end_cell);
worksheet.write_formula_with_format(current_row, start_col, formula.as_str(), &data_format)?;
// Sum Total Cost (start_col + 2)
let start_cell = rust_xlsxwriter::utility::row_col_to_cell(current_row - report.students.len() as u32, start_col + 2);
let end_cell = rust_xlsxwriter::utility::row_col_to_cell(current_row - 1, start_col + 2);
let formula = format!("=SUM({}:{})", start_cell, end_cell);
worksheet.write_formula_with_format(current_row, start_col + 2, formula.as_str(), &data_format)?;
}
}
current_row += 1;
current_row += 2; // Spacing between classes
}

View File

@@ -41,7 +41,7 @@ async fn process_attendance(input_path: String) -> Result<String, String> {
excel_writer::write_report(&output_path, reports)
.map_err(|e| format!("Failed to write Excel: {}", e))?;
Ok(format!("Successfully saved to {:?}", output_path))
Ok(format!("保存成功,文件保存到 {:?}", output_path))
}
#[cfg(test)]

View File

@@ -1,21 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="styles.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Attendance Counter</title>
<title>生成考勤表汇总数据</title>
<script type="module" src="/main.js" defer></script>
</head>
<body>
</head>
<body>
<div class="container">
<h1>Attendance Counter</h1>
<h1>生成考勤表汇总数据</h1>
<div class="card">
<p>Select the attendance Excel file to process.</p>
<button id="select-file-btn" type="button">Select Input File (.xlsx)</button>
<p>选择考勤表文件</p>
<button id="select-file-btn" type="button">选择文件 (.xlsx)</button>
<p id="selected-file" class="file-path"></p>
<button id="process-btn" type="button" disabled>开始生成</button>
<p id="status-msg"></p>
</div>
</div>
</body>
</body>
</html>

View File

@@ -2,7 +2,10 @@ const { invoke } = window.__TAURI__.core;
const { open } = window.__TAURI__.dialog;
let selectFileBtn;
let processBtn;
let statusMsg;
let selectedFileMsg;
let currentFilePath = null;
async function selectFile() {
try {
@@ -15,29 +18,47 @@ async function selectFile() {
});
if (selected) {
statusMsg.textContent = "Processing...";
currentFilePath = selected;
selectedFileMsg.textContent = "已选择: " + selected;
processBtn.disabled = false;
statusMsg.textContent = "";
statusMsg.className = "";
}
} catch (err) {
console.error(err);
statusMsg.textContent = "选择文件出错: " + err;
statusMsg.className = "error";
}
}
async function processFile() {
if (!currentFilePath) return;
statusMsg.textContent = "正在处理...";
statusMsg.className = "processing";
processBtn.disabled = true;
selectFileBtn.disabled = true;
try {
const result = await invoke("process_attendance", { inputPath: selected });
const result = await invoke("process_attendance", { inputPath: currentFilePath });
statusMsg.textContent = result;
statusMsg.className = "success";
} catch (error) {
console.error(error);
statusMsg.textContent = "Error: " + error;
statusMsg.className = "error";
}
}
} catch (err) {
console.error(err);
statusMsg.textContent = "Error selecting file: " + err;
statusMsg.textContent = "错误: " + error;
statusMsg.className = "error";
} finally {
processBtn.disabled = false;
selectFileBtn.disabled = false;
}
}
window.addEventListener("DOMContentLoaded", () => {
selectFileBtn = document.querySelector("#select-file-btn");
processBtn = document.querySelector("#process-btn");
statusMsg = document.querySelector("#status-msg");
selectedFileMsg = document.querySelector("#selected-file");
selectFileBtn.addEventListener("click", selectFile);
processBtn.addEventListener("click", processFile);
});

View File

@@ -53,6 +53,23 @@ button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
#process-btn {
margin-top: 10px;
background-color: #4472C4; /* Match Excel header color */
}
.file-path {
font-size: 0.9em;
color: #666;
margin: 10px 0;
word-break: break-all;
}
#status-msg {
margin-top: 20px;
font-weight: bold;

Binary file not shown.