[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>印刷登记快速录入系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<!-- Tailwind 配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
secondary: '#36CFC9',
accent: '#722ED1',
neutral: '#86909C',
light: '#F2F3F5',
dark: '#1D2129',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
[url=home.php?mod=space&uid=1688376]@layer[/url] utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.transition-custom {
transition: all 0.3s ease;
}
.input-focus {
[url=home.php?mod=space&uid=101791]@apply[/url] focus:ring-2 focus:ring-primary/30 focus:border-primary focus:outline-none;
}
}
</style>
</head>
<body class="bg-gray-50 font-sans text-dark">
<!-- 顶部导航栏 -->
<header class="bg-white shadow-sm sticky top-0 z-50 transition-custom">
<div class="container mx-auto px-4 py-3 flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center mb-2 md:mb-0">
<i class="fa fa-print text-primary text-2xl mr-3"></i>
<h1 class="text-xl md:text-2xl font-bold text-primary">印刷登记快速录入系统</h1>
</div>
<div class="flex items-center space-x-1 md:space-x-4 w-full md:w-auto justify-between">
<div class="flex items-center text-sm text-neutral mr-2">
<span id="sync-status" class="flex items-center">
<i class="fa fa-check-circle text-green-500 mr-1"></i>
<span>数据已同步</span>
</span>
</div>
<div class="flex space-x-2 w-full md:w-auto">
<button id="backup-btn" class="flex-1 md:flex-none px-3 py-1.5 bg-light hover:bg-gray-200 text-sm rounded transition-custom flex items-center">
<i class="fa fa-database mr-1"></i> 备份数据
</button>
<button id="export-all-btn" class="flex-1 md:flex-none px-3 py-1.5 bg-primary hover:bg-primary/90 text-white text-sm rounded transition-custom flex items-center">
<i class="fa fa-download mr-1"></i> 导出全部
</button>
</div>
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="container mx-auto px-4 py-6">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- 左侧:数据录入表单 -->
<div class="lg:col-span-1">
<div class="bg-white rounded-lg card-shadow p-5 transition-custom hover:shadow-lg">
<h2 class="text-lg font-semibold mb-4 pb-2 border-b border-gray-100">
<i class="fa fa-pencil-square-o text-primary mr-2"></i>印刷信息录入
</h2>
<form id="print-form" class="space-y-4">
<!-- 基本信息 -->
<div class="space-y-3">
<h3 class="text-sm font-medium text-neutral">基本信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="date" class="block text-sm font-medium mb-1">日期 <span class="text-red-500">*</span></label>
<input type="date" id="date" required
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
<div>
<label for="grade" class="block text-sm font-medium mb-1">年级</label>
<select id="grade" class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
<option value="">请选择</option>
<option value="初一">初一</option>
<option value="初二">初二</option>
<option value="初三">初三</option>
<option value="高一">高一</option>
<option value="高二">高二</option>
<option value="高三">高三</option>
<option value="其他">其他</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="subject" class="block text-sm font-medium mb-1">学科 <span class="text-red-500">*</span></label>
<input type="text" id="subject" required placeholder="例如:语文、数学"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
<div>
<label for="submitter" class="block text-sm font-medium mb-1">送印人 <span class="text-red-500">*</span></label>
<input type="text" id="submitter" required
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
</div>
</div>
<!-- 印刷信息 -->
<div class="space-y-3">
<h3 class="text-sm font-medium text-neutral">印刷信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="paper-size" class="block text-sm font-medium mb-1">纸张规格</label>
<select id="paper-size" class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
<option value="A3">A3</option>
<option value="A4" selected>A4</option>
<option value="A5">A5</option>
<option value="B4">B4</option>
<option value="B5">B5</option>
<option value="其他">其他</option>
</select>
</div>
<div>
<label for="print-type" class="block text-sm font-medium mb-1">单双 <span class="text-red-500">*</span></label>
<select id="print-type" required class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
<option value="单面">单面</option>
<option value="双面" selected>双面</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="paper-count" class="block text-sm font-medium mb-1">印刷纸张 <span class="text-red-500">*</span></label>
<input type="number" id="paper-count" required min="1" value="1"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
<div>
<label for="copy-count" class="block text-sm font-medium mb-1">印刷份数 <span class="text-red-500">*</span></label>
<input type="number" id="copy-count" required min="1" value="50"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
</div>
<div>
<label for="plate-count" class="block text-sm font-medium mb-1">制版数量</label>
<input type="number" id="plate-count" min="0" value="0"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
</div>
<!-- 费用信息 -->
<div class="space-y-3">
<h3 class="text-sm font-medium text-neutral">费用信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="total-pages" class="block text-sm font-medium mb-1">印刷总数</label>
<input type="number" id="total-pages" readonly
class="w-full px-3 py-2 bg-gray-50 border border-gray-300 rounded input-focus transition-custom">
</div>
<div>
<label for="unit-price" class="block text-sm font-medium mb-1">单价(元) <span class="text-red-500">*</span></label>
<input type="number" id="unit-price" required min="0" step="0.01" value="0.1"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="total-amount" class="block text-sm font-medium mb-1">金额(元)</label>
<input type="number" id="total-amount" readonly
class="w-full px-3 py-2 bg-gray-50 border border-gray-300 rounded input-focus transition-custom">
</div>
<div>
<label for="expense-type" class="block text-sm font-medium mb-1">类型</label>
<select id="expense-type" class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
<option value="教学资料">教学资料</option>
<option value="考试试卷" selected>考试试卷</option>
<option value="办公文件">办公文件</option>
<option value="其他">其他</option>
</select>
</div>
</div>
<div>
<label for="responsible-person" class="block text-sm font-medium mb-1">负责人</label>
<input type="text" id="responsible-person"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
</div>
<!-- 备注信息 -->
<div>
<label for="notes" class="block text-sm font-medium mb-1">备注</label>
<textarea id="notes" rows="3" placeholder="请输入备注信息..."
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom"></textarea>
</div>
<!-- 操作按钮 -->
<div class="flex space-x-3 pt-2">
<button type="reset" id="reset-btn"
class="flex-1 px-4 py-2 border border-gray-300 rounded hover:bg-gray-50 transition-custom text-neutral">
<i class="fa fa-refresh mr-1"></i> 重置
</button>
<button type="submit" id="save-btn"
class="flex-1 px-4 py-2 bg-primary text-white rounded hover:bg-primary/90 transition-custom">
<i class="fa fa-save mr-1"></i> 保存记录
</button>
</div>
</form>
</div>
</div>
<!-- 右侧:数据列表 -->
<div class="lg:col-span-2">
<div class="bg-white rounded-lg card-shadow p-5 transition-custom hover:shadow-lg">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-4 pb-2 border-b border-gray-100">
<h2 class="text-lg font-semibold mb-2 md:mb-0">
<i class="fa fa-list-alt text-primary mr-2"></i>印刷记录列表
</h2>
<div class="w-full md:w-auto flex space-x-2">
<div class="relative flex-1 md:flex-none md:w-64">
<input type="text" id="search-input" placeholder="搜索..."
class="w-full pl-9 pr-3 py-2 border border-gray-300 rounded input-focus transition-custom">
<i class="fa fa-search absolute left-3 top-1/2 -translate-y-1/2 text-neutral"></i>
</div>
<button id="export-selected-btn" class="px-3 py-2 bg-secondary hover:bg-secondary/90 text-white rounded transition-custom flex items-center">
<i class="fa fa-file-excel-o mr-1"></i> 导出选中
</button>
</div>
</div>
<!-- 数据表格 -->
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead>
<tr>
<th class="px-3 py-3 bg-gray-50 text-left text-xs font-medium text-neutral uppercase tracking-wider w-10">
<input type="checkbox" id="select-all">
</th>
<th class="sortable px-3 py-3 bg-gray-50 text-left text-xs font-medium text-neutral uppercase tracking-wider cursor-pointer" data-sort="date">
日期 <i class="fa fa-sort ml-1"></i>
</th>
<th class="sortable px-3 py-3 bg-gray-50 text-left text-xs font-medium text-neutral uppercase tracking-wider cursor-pointer" data-sort="grade">
年级 <i class="fa fa-sort ml-1"></i>
</th>
<th class="sortable px-3 py-3 bg-gray-50 text-left text-xs font-medium text-neutral uppercase tracking-wider cursor-pointer" data-sort="subject">
学科 <i class="fa fa-sort ml-1"></i>
</th>
<th class="sortable px-3 py-3 bg-gray-50 text-left text-xs font-medium text-neutral uppercase tracking-wider cursor-pointer" data-sort="submitter">
送印人 <i class="fa fa-sort ml-1"></i>
</th>
<th class="sortable px-3 py-3 bg-gray-50 text-left text-xs font-medium text-neutral uppercase tracking-wider cursor-pointer" data-sort="printType">
单双 <i class="fa fa-sort ml-1"></i>
</th>
<th class="sortable px-3 py-3 bg-gray-50 text-left text-xs font-medium text-neutral uppercase tracking-wider cursor-pointer" data-sort="totalPages">
总数 <i class="fa fa-sort ml-1"></i>
</th>
<th class="sortable px-3 py-3 bg-gray-50 text-left text-xs font-medium text-neutral uppercase tracking-wider cursor-pointer" data-sort="totalAmount">
金额 <i class="fa fa-sort ml-1"></i>
</th>
<th class="px-3 py-3 bg-gray-50 text-left text-xs font-medium text-neutral uppercase tracking-wider">
操作
</th>
</tr>
</thead>
<tbody id="records-table-body" class="bg-white divide-y divide-gray-200">
<!-- 数据行会通过JavaScript动态插入 -->
<tr class="text-center">
<td colspan="9" class="px-3 py-8 text-neutral">
<i class="fa fa-info-circle mr-2"></i>暂无记录,请添加数据
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页控制 -->
<div class="flex flex-col sm:flex-row justify-between items-center mt-4 pt-3 border-t border-gray-100">
<div class="text-sm text-neutral mb-3 sm:mb-0">
显示 <span id="start-record">0</span>-<span id="end-record">0</span> 条,共 <span id="total-records">0</span> 条
</div>
<div class="flex items-center space-x-1">
<select id="page-size" class="px-2 py-1 border border-gray-300 rounded text-sm input-focus">
<option value="10">10条/页</option>
<option value="20">20条/页</option>
<option value="50">50条/页</option>
<option value="100">100条/页</option>
</select>
<div class="flex items-center">
<button id="prev-page" class="w-8 h-8 flex items-center justify-center rounded border border-gray-300 text-neutral hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i class="fa fa-chevron-left text-xs"></i>
</button>
<span id="current-page" class="mx-2 text-sm">第 1 页</span>
<button id="next-page" class="w-8 h-8 flex items-center justify-center rounded border border-gray-300 text-neutral hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i class="fa fa-chevron-right text-xs"></i>
</button>
</div>
</div>
</div>
</div>
<!-- 数据统计图表 -->
<div class="bg-white rounded-lg card-shadow p-5 mt-6 transition-custom hover:shadow-lg">
<h2 class="text-lg font-semibold mb-4 pb-2 border-b border-gray-100">
<i class="fa fa-bar-chart text-primary mr-2"></i>印刷数据统计
</h2>
<div class="h-64">
<canvas id="statistics-chart"></canvas>
</div>
</div>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-white border-t border-gray-200 mt-8">
<div class="container mx-auto px-4 py-4 text-center text-sm text-neutral">
<p>© 2023 印刷登记快速录入系统 - 增强版 | 版权所有</p>
</div>
</footer>
<!-- 提示框 -->
<div id="toast" class="fixed bottom-5 right-5 px-4 py-3 rounded shadow-lg transform translate-y-20 opacity-0 transition-all duration-300 flex items-center z-50">
<i id="toast-icon" class="mr-2"></i>
<span id="toast-message"></span>
</div>
<!-- 编辑模态框 -->
<div id="edit-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto m-4 transform transition-all scale-95 opacity-0" id="modal-content">
<div class="p-5 border-b border-gray-100 flex justify-between items-center sticky top-0 bg-white z-10">
<h3 class="text-lg font-semibold">编辑印刷记录</h3>
<button id="close-modal" class="text-neutral hover:text-dark transition-custom">
<i class="fa fa-times text-xl"></i>
</button>
</div>
<div class="p-5">
<!-- 编辑表单会通过JavaScript动态生成 -->
<form id="edit-form" class="space-y-4">
<!-- 表单内容将由JS动态生成 -->
</form>
</div>
</div>
</div>
<script>
// 全局变量
let records = JSON.parse(localStorage.getItem('printingRecords')) || [];
let currentPage = 1;
let pageSize = 10;
let sortField = null;
let sortDirection = 'asc';
let searchTerm = '';
let chart = null;
// DOM 元素
const printForm = document.getElementById('print-form');
const recordsTableBody = document.getElementById('records-table-body');
const totalRecordsElement = document.getElementById('total-records');
const startRecordElement = document.getElementById('start-record');
const endRecordElement = document.getElementById('end-record');
const currentPageElement = document.getElementById('current-page');
const prevPageButton = document.getElementById('prev-page');
const nextPageButton = document.getElementById('next-page');
const pageSizeSelect = document.getElementById('page-size');
const searchInput = document.getElementById('search-input');
const selectAllCheckbox = document.getElementById('select-all');
const exportSelectedButton = document.getElementById('export-selected-btn');
const exportAllButton = document.getElementById('export-all-btn');
const backupButton = document.getElementById('backup-btn');
const editModal = document.getElementById('edit-modal');
const modalContent = document.getElementById('modal-content');
const closeModalButton = document.getElementById('close-modal');
const editForm = document.getElementById('edit-form');
// 自动计算相关字段
const paperCountInput = document.getElementById('paper-count');
const copyCountInput = document.getElementById('copy-count');
const printTypeSelect = document.getElementById('print-type');
const totalPagesInput = document.getElementById('total-pages');
const unitPriceInput = document.getElementById('unit-price');
const totalAmountInput = document.getElementById('total-amount');
// 初始化日期为今天
document.getElementById('date').valueAsDate = new Date();
// 初始化
document.addEventListener('DOMContentLoaded', () => {
calculateTotalPages();
calculateTotalAmount();
renderRecords();
initChart();
// 为影响计算的字段添加事件监听器
paperCountInput.addEventListener('input', calculateTotalPages);
copyCountInput.addEventListener('input', calculateTotalPages);
printTypeSelect.addEventListener('change', calculateTotalPages);
unitPriceInput.addEventListener('input', calculateTotalAmount);
// 表单提交
printForm.addEventListener('submit', handleFormSubmit);
// 分页控制
prevPageButton.addEventListener('click', goToPrevPage);
nextPageButton.addEventListener('click', goToNextPage);
pageSizeSelect.addEventListener('change', handlePageSizeChange);
// 搜索功能
searchInput.addEventListener('input', handleSearch);
// 全选功能
selectAllCheckbox.addEventListener('change', handleSelectAll);
// 导出功能
exportSelectedButton.addEventListener('click', exportSelectedRecords);
exportAllButton.addEventListener('click', exportAllRecords);
// 备份功能
backupButton.addEventListener('click', backupData);
// 排序功能
document.querySelectorAll('.sortable').forEach(header => {
header.addEventListener('click', () => handleSort(header.dataset.sort));
});
// 模态框控制
closeModalButton.addEventListener('click', closeModal);
editModal.addEventListener('click', (e) => {
if (e.target === editModal) closeModal();
});
});
// 计算印刷总数
function calculateTotalPages() {
const paperCount = parseInt(paperCountInput.value) || 0;
const copyCount = parseInt(copyCountInput.value) || 0;
const isDoubleSided = printTypeSelect.value === '双面';
// 修复计算逻辑:双面印刷时纸张数 × 2
const totalPages = paperCount * copyCount * (isDoubleSided ? 2 : 1);
totalPagesInput.value = totalPages;
// 重新计算总金额
calculateTotalAmount();
}
// 计算总金额
function calculateTotalAmount() {
const totalPages = parseInt(totalPagesInput.value) || 0;
const unitPrice = parseFloat(unitPriceInput.value) || 0;
// 修复计算逻辑:保留两位小数
const totalAmount = (totalPages * unitPrice).toFixed(2);
totalAmountInput.value = totalAmount;
}
// 处理表单提交
function handleFormSubmit(e) {
e.preventDefault();
// 收集表单数据
const newRecord = {
id: Date.now().toString(), // 使用时间戳作为唯一ID
date: document.getElementById('date').value,
grade: document.getElementById('grade').value,
subject: document.getElementById('subject').value,
submitter: document.getElementById('submitter').value,
paperSize: document.getElementById('paper-size').value,
printType: document.getElementById('print-type').value,
paperCount: parseInt(document.getElementById('paper-count').value) || 0,
copyCount: parseInt(document.getElementById('copy-count').value) || 0,
plateCount: parseInt(document.getElementById('plate-count').value) || 0,
totalPages: parseInt(totalPagesInput.value) || 0,
unitPrice: parseFloat(unitPriceInput.value) || 0,
totalAmount: parseFloat(totalAmountInput.value) || 0,
expenseType: document.getElementById('expense-type').value,
responsiblePerson: document.getElementById('responsible-person').value,
notes: document.getElementById('notes').value,
createdAt: new Date().toISOString()
};
// 添加新记录
records.unshift(newRecord); // 添加到开头
saveRecords();
renderRecords();
updateChart();
// 重置表单
printForm.reset();
document.getElementById('date').valueAsDate = new Date();
calculateTotalPages();
// 显示成功提示
showToast('记录保存成功', 'success');
}
// 保存记录到本地存储
function saveRecords() {
localStorage.setItem('printingRecords', JSON.stringify(records));
updateSyncStatus(true);
}
// 更新同步状态
function updateSyncStatus(isSynced) {
const syncStatus = document.getElementById('sync-status');
if (isSynced) {
syncStatus.innerHTML = '<i class="fa fa-check-circle text-green-500 mr-1"></i><span>数据已同步</span>';
} else {
syncStatus.innerHTML = '<i class="fa fa-exclamation-triangle text-yellow-500 mr-1"></i><span>数据未同步</span>';
}
}
// 渲染记录列表
function renderRecords() {
// 过滤记录
let filteredRecords = records;
if (searchTerm) {
const term = searchTerm.toLowerCase();
filteredRecords = records.filter(record =>
record.date.toLowerCase().includes(term) ||
record.grade.toLowerCase().includes(term) ||
record.subject.toLowerCase().includes(term) ||
record.submitter.toLowerCase().includes(term) ||
record.printType.toLowerCase().includes(term) ||
record.responsiblePerson.toLowerCase().includes(term) ||
record.notes.toLowerCase().includes(term) ||
record.totalPages.toString().includes(term) ||
record.totalAmount.toString().includes(term)
);
}
// 排序记录
if (sortField) {
filteredRecords.sort((a, b) => {
if (a[sortField] < b[sortField]) return sortDirection === 'asc' ? -1 : 1;
if (a[sortField] > b[sortField]) return sortDirection === 'asc' ? 1 : -1;
return 0;
});
}
// 分页处理
const totalPages = Math.ceil(filteredRecords.length / pageSize);
currentPage = Math.min(currentPage, totalPages || 1); // 确保当前页不超过总页数
const startIndex = (currentPage - 1) * pageSize;
const endIndex = Math.min(startIndex + pageSize, filteredRecords.length);
const currentRecords = filteredRecords.slice(startIndex, endIndex);
// 更新分页信息
totalRecordsElement.textContent = filteredRecords.length;
startRecordElement.textContent = filteredRecords.length > 0 ? startIndex + 1 : 0;
endRecordElement.textContent = endIndex;
currentPageElement.textContent = `第 ${currentPage} 页`;
// 更新分页按钮状态
prevPageButton.disabled = currentPage === 1;
nextPageButton.disabled = currentPage === totalPages || totalPages === 0;
// 清空表格
recordsTableBody.innerHTML = '';
// 没有记录时显示提示
if (currentRecords.length === 0) {
const emptyRow = document.createElement('tr');
emptyRow.className = 'text-center';
emptyRow.innerHTML = `<td colspan="9" class="px-3 py-8 text-neutral">
<i class="fa fa-info-circle mr-2"></i>${searchTerm ? '没有找到匹配的记录' : '暂无记录,请添加数据'}
</td>`;
recordsTableBody.appendChild(emptyRow);
return;
}
// 添加记录行
currentRecords.forEach(record => {
const row = document.createElement('tr');
row.className = 'hover:bg-gray-50 transition-custom';
row.dataset.id = record.id;
row.innerHTML = `
<td class="px-3 py-3 whitespace-nowrap">
<input type="checkbox" class="record-select" data-id="${record.id}">
</td>
<td class="px-3 py-3 whitespace-nowrap">${formatDate(record.date)}</td>
<td class="px-3 py-3 whitespace-nowrap">${record.grade || '-'}</td>
<td class="px-3 py-3 whitespace-nowrap">${record.subject}</td>
<td class="px-3 py-3 whitespace-nowrap">${record.submitter}</td>
<td class="px-3 py-3 whitespace-nowrap">${record.printType}</td>
<td class="px-3 py-3 whitespace-nowrap">${record.totalPages}</td>
<td class="px-3 py-3 whitespace-nowrap">${record.totalAmount.toFixed(2)}</td>
<td class="px-3 py-3 whitespace-nowrap">
<div class="flex space-x-1">
<button class="edit-btn text-blue-500 hover:text-blue-700 p-1" title="编辑">
<i class="fa fa-pencil"></i>
</button>
<button class="delete-btn text-red-500 hover:text-red-700 p-1" title="删除">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
`;
recordsTableBody.appendChild(row);
// 添加编辑和删除事件
row.querySelector('.edit-btn').addEventListener('click', () => editRecord(record.id));
row.querySelector('.delete-btn').addEventListener('click', () => deleteRecord(record.id));
});
// 更新选择状态
updateSelectAllStatus();
}
// 格式化日期
function formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
}
// 处理分页
function goToPrevPage() {
if (currentPage > 1) {
currentPage--;
renderRecords();
}
}
function goToNextPage() {
const totalPages = Math.ceil(getFilteredRecordsCount() / pageSize);
if (currentPage < totalPages) {
currentPage++;
renderRecords();
}
}
function handlePageSizeChange() {
pageSize = parseInt(pageSizeSelect.value);
currentPage = 1; // 重置到第一页
renderRecords();
}
// 获取过滤后的记录数量
function getFilteredRecordsCount() {
if (!searchTerm) return records.length;
const term = searchTerm.toLowerCase();
return records.filter(record =>
record.date.toLowerCase().includes(term) ||
record.grade.toLowerCase().includes(term) ||
record.subject.toLowerCase().includes(term) ||
record.submitter.toLowerCase().includes(term) ||
record.printType.toLowerCase().includes(term) ||
record.responsiblePerson.toLowerCase().includes(term) ||
record.notes.toLowerCase().includes(term) ||
record.totalPages.toString().includes(term) ||
record.totalAmount.toString().includes(term)
).length;
}
// 处理搜索
function handleSearch() {
searchTerm = searchInput.value.trim();
currentPage = 1; // 重置到第一页
renderRecords();
}
// 处理排序
function handleSort(field) {
// 如果点击的是当前排序字段,则切换排序方向
if (sortField === field) {
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
} else {
// 否则设置新的排序字段并默认升序
sortField = field;
sortDirection = 'asc';
}
// 更新排序图标
document.querySelectorAll('.sortable i').forEach(icon => {
icon.className = 'fa fa-sort ml-1';
});
const currentIcon = document.querySelector(`.sortable[data-sort="${field}"] i`);
currentIcon.className = `fa fa-sort-${sortDirection} ml-1`;
renderRecords();
}
// 处理全选
function handleSelectAll() {
const isChecked = selectAllCheckbox.checked;
document.querySelectorAll('.record-select').forEach(checkbox => {
checkbox.checked = isChecked;
});
}
// 更新全选状态
function updateSelectAllStatus() {
const checkboxes = document.querySelectorAll('.record-select');
if (checkboxes.length === 0) {
selectAllCheckbox.checked = false;
return;
}
const allChecked = Array.from(checkboxes).every(checkbox => checkbox.checked);
selectAllCheckbox.checked = allChecked;
}
// 导出选中的记录
function exportSelectedRecords() {
const selectedIds = Array.from(document.querySelectorAll('.record-select:checked'))
.map(checkbox => checkbox.dataset.id);
if (selectedIds.length === 0) {
showToast('请先选择要导出的记录', 'warning');
return;
}
const selectedRecords = records.filter(record => selectedIds.includes(record.id));
exportRecordsToExcel(selectedRecords, '印刷记录_选中');
}
// 导出所有记录
function exportAllRecords() {
if (records.length === 0) {
showToast('没有记录可导出', 'warning');
return;
}
// 过滤记录(考虑搜索条件)
let recordsToExport = records;
if (searchTerm) {
const term = searchTerm.toLowerCase();
recordsToExport = records.filter(record =>
record.date.toLowerCase().includes(term) ||
record.grade.toLowerCase().includes(term) ||
record.subject.toLowerCase().includes(term) ||
record.submitter.toLowerCase().includes(term) ||
record.printType.toLowerCase().includes(term) ||
record.responsiblePerson.toLowerCase().includes(term) ||
record.notes.toLowerCase().includes(term) ||
record.totalPages.toString().includes(term) ||
record.totalAmount.toString().includes(term)
);
}
exportRecordsToExcel(recordsToExport, '印刷记录_全部');
}
// 导出记录到Excel
function exportRecordsToExcel(data, fileName) {
// 准备导出数据(只包含需要的字段)
const exportData = data.map(record => ({
'日期': formatDate(record.date),
'年级': record.grade || '',
'学科': record.subject,
'送印人': record.submitter,
'纸张规格': record.paperSize,
'单双': record.printType,
'印刷纸张': record.paperCount,
'印刷份数': record.copyCount,
'制版数量': record.plateCount,
'印刷总数': record.totalPages,
'单价(元)': record.unitPrice,
'金额(元)': record.totalAmount,
'类型': record.expenseType,
'负责人': record.responsiblePerson || '',
'备注': record.notes || ''
}));
// 创建工作簿和工作表
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '印刷记录');
// 修复导出逻辑:使用正确的文件名和格式
XLSX.writeFile(wb, `${fileName}_${new Date().toLocaleDateString().replace(/\//g, '-')}.xlsx`);
showToast(`成功导出 ${exportData.length} 条记录`, 'success');
}
// 备份数据
function backupData() {
if (records.length === 0) {
showToast('没有数据可备份', 'warning');
return;
}
// 创建备份数据
const backupData = {
records: records,
timestamp: new Date().toISOString(),
version: '1.0'
};
// 创建下载链接
const blob = new Blob([JSON.stringify(backupData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `印刷记录备份_${new Date().toLocaleDateString().replace(/\//g, '-')}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('数据备份成功', 'success');
}
// 编辑记录
function editRecord(id) {
const record = records.find(r => r.id === id);
if (!record) return;
// 生成编辑表单
editForm.innerHTML = `
<input type="hidden" id="edit-id" value="${record.id}">
<!-- 基本信息 -->
<div class="space-y-3">
<h3 class="text-sm font-medium text-neutral">基本信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="edit-date" class="block text-sm font-medium mb-1">日期 <span class="text-red-500">*</span></label>
<input type="date" id="edit-date" required value="${record.date}"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
<div>
<label for="edit-grade" class="block text-sm font-medium mb-1">年级</label>
<select id="edit-grade" class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
<option value="">请选择</option>
<option value="初一" ${record.grade === '初一' ? 'selected' : ''}>初一</option>
<option value="初二" ${record.grade === '初二' ? 'selected' : ''}>初二</option>
<option value="初三" ${record.grade === '初三' ? 'selected' : ''}>初三</option>
<option value="高一" ${record.grade === '高一' ? 'selected' : ''}>高一</option>
<option value="高二" ${record.grade === '高二' ? 'selected' : ''}>高二</option>
<option value="高三" ${record.grade === '高三' ? 'selected' : ''}>高三</option>
<option value="其他" ${record.grade === '其他' ? 'selected' : ''}>其他</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="edit-subject" class="block text-sm font-medium mb-1">学科 <span class="text-red-500">*</span></label>
<input type="text" id="edit-subject" required value="${record.subject}"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
<div>
<label for="edit-submitter" class="block text-sm font-medium mb-1">送印人 <span class="text-red-500">*</span></label>
<input type="text" id="edit-submitter" required value="${record.submitter}"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
</div>
</div>
<!-- 印刷信息 -->
<div class="space-y-3">
<h3 class="text-sm font-medium text-neutral">印刷信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="edit-paper-size" class="block text-sm font-medium mb-1">纸张规格</label>
<select id="edit-paper-size" class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
<option value="A3" ${record.paperSize === 'A3' ? 'selected' : ''}>A3</option>
<option value="A4" ${record.paperSize === 'A4' ? 'selected' : ''}>A4</option>
<option value="A5" ${record.paperSize === 'A5' ? 'selected' : ''}>A5</option>
<option value="B4" ${record.paperSize === 'B4' ? 'selected' : ''}>B4</option>
<option value="B5" ${record.paperSize === 'B5' ? 'selected' : ''}>B5</option>
<option value="其他" ${record.paperSize === '其他' ? 'selected' : ''}>其他</option>
</select>
</div>
<div>
<label for="edit-print-type" class="block text-sm font-medium mb-1">单双 <span class="text-red-500">*</span></label>
<select id="edit-print-type" required class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
<option value="单面" ${record.printType === '单面' ? 'selected' : ''}>单面</option>
<option value="双面" ${record.printType === '双面' ? 'selected' : ''}>双面</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="edit-paper-count" class="block text-sm font-medium mb-1">印刷纸张 <span class="text-red-500">*</span></label>
<input type="number" id="edit-paper-count" required min="1" value="${record.paperCount}"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
<div>
<label for="edit-copy-count" class="block text-sm font-medium mb-1">印刷份数 <span class="text-red-500">*</span></label>
<input type="number" id="edit-copy-count" required min="1" value="${record.copyCount}"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
</div>
<div>
<label for="edit-plate-count" class="block text-sm font-medium mb-1">制版数量</label>
<input type="number" id="edit-plate-count" min="0" value="${record.plateCount}"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
</div>
<!-- 费用信息 -->
<div class="space-y-3">
<h3 class="text-sm font-medium text-neutral">费用信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="edit-total-pages" class="block text-sm font-medium mb-1">印刷总数</label>
<input type="number" id="edit-total-pages" readonly value="${record.totalPages}"
class="w-full px-3 py-2 bg-gray-50 border border-gray-300 rounded input-focus transition-custom">
</div>
<div>
<label for="edit-unit-price" class="block text-sm font-medium mb-1">单价(元) <span class="text-red-500">*</span></label>
<input type="number" id="edit-unit-price" required min="0" step="0.01" value="${record.unitPrice}"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label for="edit-total-amount" class="block text-sm font-medium mb-1">金额(元)</label>
<input type="number" id="edit-total-amount" readonly value="${record.totalAmount}"
class="w-full px-3 py-2 bg-gray-50 border border-gray-300 rounded input-focus transition-custom">
</div>
<div>
<label for="edit-expense-type" class="block text-sm font-medium mb-1">类型</label>
<select id="edit-expense-type" class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
<option value="教学资料" ${record.expenseType === '教学资料' ? 'selected' : ''}>教学资料</option>
<option value="考试试卷" ${record.expenseType === '考试试卷' ? 'selected' : ''}>考试试卷</option>
<option value="办公文件" ${record.expenseType === '办公文件' ? 'selected' : ''}>办公文件</option>
<option value="其他" ${record.expenseType === '其他' ? 'selected' : ''}>其他</option>
</select>
</div>
</div>
<div>
<label for="edit-responsible-person" class="block text-sm font-medium mb-1">负责人</label>
<input type="text" id="edit-responsible-person" value="${record.responsiblePerson || ''}"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">
</div>
</div>
<!-- 备注信息 -->
<div>
<label for="edit-notes" class="block text-sm font-medium mb-1">备注</label>
<textarea id="edit-notes" rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded input-focus transition-custom">${record.notes || ''}</textarea>
</div>
<!-- 操作按钮 -->
<div class="flex space-x-3 pt-2">
<button type="button" id="cancel-edit"
class="flex-1 px-4 py-2 border border-gray-300 rounded hover:bg-gray-50 transition-custom text-neutral">
<i class="fa fa-times mr-1"></i> 取消
</button>
<button type="submit"
class="flex-1 px-4 py-2 bg-primary text-white rounded hover:bg-primary/90 transition-custom">
<i class="fa fa-save mr-1"></i> 保存修改
</button>
</div>
`;
// 添加编辑表单的计算逻辑
const editPaperCount = document.getElementById('edit-paper-count');
const editCopyCount = document.getElementById('edit-copy-count');
const editPrintType = document.getElementById('edit-print-type');
const editTotalPages = document.getElementById('edit-total-pages');
const editUnitPrice = document.getElementById('edit-unit-price');
const editTotalAmount = document.getElementById('edit-total-amount');
// 计算函数
function calculateEditTotalPages() {
const paperCount = parseInt(editPaperCount.value) || 0;
const copyCount = parseInt(editCopyCount.value) || 0;
const isDoubleSided = editPrintType.value === '双面';
const totalPages = paperCount * copyCount * (isDoubleSided ? 2 : 1);
editTotalPages.value = totalPages;
calculateEditTotalAmount();
}
function calculateEditTotalAmount() {
const totalPages = parseInt(editTotalPages.value) || 0;
const unitPrice = parseFloat(editUnitPrice.value) || 0;
const totalAmount = (totalPages * unitPrice).toFixed(2);
editTotalAmount.value = totalAmount;
}
// 添加事件监听
editPaperCount.addEventListener('input', calculateEditTotalPages);
editCopyCount.addEventListener('input', calculateEditTotalPages);
editPrintType.addEventListener('change', calculateEditTotalPages);
editUnitPrice.addEventListener('input', calculateEditTotalAmount);
// 取消编辑按钮
document.getElementById('cancel-edit').addEventListener('click', closeModal);
// 编辑表单提交
editForm.addEventListener('submit', (e) => {
e.preventDefault();
const recordId = document.getElementById('edit-id').value;
const recordIndex = records.findIndex(r => r.id === recordId);
if (recordIndex !== -1) {
// 更新记录
records[recordIndex] = {
...records[recordIndex],
date: document.getElementById('edit-date').value,
grade: document.getElementById('edit-grade').value,
subject: document.getElementById('edit-subject').value,
submitter: document.getElementById('edit-submitter').value,
paperSize: document.getElementById('edit-paper-size').value,
printType: document.getElementById('edit-print-type').value,
paperCount: parseInt(document.getElementById('edit-paper-count').value) || 0,
copyCount: parseInt(document.getElementById('edit-copy-count').value) || 0,
plateCount: parseInt(document.getElementById('edit-plate-count').value) || 0,
totalPages: parseInt(editTotalPages.value) || 0,
unitPrice: parseFloat(editUnitPrice.value) || 0,
totalAmount: parseFloat(editTotalAmount.value) || 0,
expenseType: document.getElementById('edit-expense-type').value,
responsiblePerson: document.getElementById('edit-responsible-person').value,
notes: document.getElementById('edit-notes').value
};
saveRecords();
renderRecords();
updateChart();
closeModal();
showToast('记录更新成功', 'success');
}
});
// 显示模态框
openModal();
}
// 删除记录
function deleteRecord(id) {
if (confirm('确定要删除这条记录吗?此操作不可撤销。')) {
const initialLength = records.length;
records = records.filter(record => record.id !== id);
if (records.length < initialLength) {
saveRecords();
renderRecords();
updateChart();
showToast('记录已删除', 'success');
}
}
}
// 打开模态框
function openModal() {
editModal.classList.remove('hidden');
// 触发重排后添加动画类
setTimeout(() => {
modalContent.classList.remove('scale-95', 'opacity-0');
modalContent.classList.add('scale-100', 'opacity-100');
}, 10);
}
// 关闭模态框
function closeModal() {
modalContent.classList.remove('scale-100', 'opacity-100');
modalContent.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
editModal.classList.add('hidden');
}, 300);
}
// 显示提示框
function showToast(message, type = 'info') {
const toast = document.getElementById('toast');
const toastIcon = document.getElementById('toast-icon');
const toastMessage = document.getElementById('toast-message');
// 设置图标和样式
switch (type) {
case 'success':
toast.className = 'fixed bottom-5 right-5 px-4 py-3 rounded shadow-lg bg-green-50 text-green-700 transform translate-y-20 opacity-0 transition-all duration-300 flex items-center z-50';
toastIcon.className = 'fa fa-check-circle mr-2';
break;
case 'error':
toast.className = 'fixed bottom-5 right-5 px-4 py-3 rounded shadow-lg bg-red-50 text-red-700 transform translate-y-20 opacity-0 transition-all duration-300 flex items-center z-50';
toastIcon.className = 'fa fa-times-circle mr-2';
break;
case 'warning':
toast.className = 'fixed bottom-5 right-5 px-4 py-3 rounded shadow-lg bg-yellow-50 text-yellow-700 transform translate-y-20 opacity-0 transition-all duration-300 flex items-center z-50';
toastIcon.className = 'fa fa-exclamation-triangle mr-2';
break;
default:
toast.className = 'fixed bottom-5 right-5 px-4 py-3 rounded shadow-lg bg-blue-50 text-blue-700 transform translate-y-20 opacity-0 transition-all duration-300 flex items-center z-50';
toastIcon.className = 'fa fa-info-circle mr-2';
}
// 设置消息
toastMessage.textContent = message;
// 显示提示框
setTimeout(() => {
toast.classList.remove('translate-y-20', 'opacity-0');
toast.classList.add('translate-y-0', 'opacity-100');
}, 10);
// 3秒后隐藏
setTimeout(() => {
toast.classList.remove('translate-y-0', 'opacity-100');
toast.classList.add('translate-y-20', 'opacity-0');
}, 3000);
}
// 初始化图表
function initChart() {
const ctx = document.getElementById('statistics-chart').getContext('2d');
// 准备图表数据
const chartData = getChartData();
// 创建图表
chart = new Chart(ctx, {
type: 'bar',
data: {
labels: chartData.labels,
datasets: [{
label: '印刷总数',
data: chartData.totalPages,
backgroundColor: 'rgba(22, 93, 255, 0.6)',
borderColor: 'rgba(22, 93, 255, 1)',
borderWidth: 1
}, {
label: '总金额(元)',
data: chartData.totalAmounts,
backgroundColor: 'rgba(54, 207, 201, 0.6)',
borderColor: 'rgba(54, 207, 201, 1)',
borderWidth: 1,
yAxisID: 'y1'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '印刷总数'
}
},
y1: {
beginAtZero: true,
position: 'right',
title: {
display: true,
text: '总金额(元)'
},
grid: {
drawOnChartArea: false
}
}
},
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: '近7天印刷统计'
}
}
}
});
}
// 更新图表
function updateChart() {
if (!chart) return;
const chartData = getChartData();
chart.data.labels = chartData.labels;
chart.data.datasets[0].data = chartData.totalPages;
chart.data.datasets[1].data = chartData.totalAmounts;
chart.update();
}
// 获取图表数据
function getChartData() {
// 获取近7天的日期
const labels = [];
const totalPages = [];
const totalAmounts = [];
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = `${date.getMonth() + 1}月${date.getDate()}日`;
const dateKey = date.toISOString().split('T')[0]; // YYYY-MM-DD
labels.push(dateStr);
// 计算当天的总印刷数和总金额
const dayRecords = records.filter(r => r.date === dateKey);
const pages = dayRecords.reduce((sum, r) => sum + r.totalPages, 0);
const amount = dayRecords.reduce((sum, r) => sum + r.totalAmount, 0);
totalPages.push(pages);
totalAmounts.push(amount.toFixed(2));
}
return { labels, totalPages, totalAmounts };
}
</script>
</body>
</html>