1321 lines
52 KiB
JavaScript
1321 lines
52 KiB
JavaScript
(() => {
|
||
Router.register('/plan-mgmt/material-purchase', async () => {
|
||
return `
|
||
<div class="page-container">
|
||
<div class="page-header">
|
||
<div>
|
||
<h2>物料清单-采购</h2>
|
||
<span id="selection-info" class="selection-info" style="display: none;">已选择 0 项</span>
|
||
</div>
|
||
<div class="page-actions">
|
||
<button class="btn btn-primary" onclick="MaterialPurchase.showAddDialog()">
|
||
<span>➕</span> 新增物料需求
|
||
</button>
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.showImportDialog()" title="从Excel导入">
|
||
<span>📥</span> 导入Excel
|
||
</button>
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.exportSelected()" title="导出选中项">
|
||
<span>📤</span> 导出选中
|
||
</button>
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.exportData()">
|
||
<span>📤</span> 全部导出
|
||
</button>
|
||
<button class="btn btn-danger" onclick="MaterialPurchase.deleteSelected()" title="删除选中项">
|
||
<span>🗑️</span> 批量删除
|
||
</button>
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.showRecycleBin()" title="查看回收站">
|
||
<span>♻️</span> 回收站
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="filter-section">
|
||
<div class="filter-row">
|
||
<input type="text" id="search-keyword" placeholder="搜索标题、编号、物料编码..." class="input-field" />
|
||
<select id="filter-status" class="input-field">
|
||
<option value="">全部状态</option>
|
||
<option value="pending">待处理</option>
|
||
<option value="processing">处理中</option>
|
||
<option value="completed">已完成</option>
|
||
</select>
|
||
<select id="filter-demand-status" class="input-field">
|
||
<option value="">全部需求状态</option>
|
||
<option value="urgent">紧急</option>
|
||
<option value="normal">正常</option>
|
||
<option value="low">低优先级</option>
|
||
</select>
|
||
<button class="btn btn-primary" onclick="MaterialPurchase.search()">搜索</button>
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.resetFilter()">重置</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-container" style="overflow-x: auto;">
|
||
<table class="data-table" id="material-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="min-width: 50px; text-align: center;">
|
||
<label class="custom-checkbox"><input type="checkbox" id="select-all-checkbox" onchange="MaterialPurchase.toggleSelectAll(this)" /><span class="checkmark"></span></label>
|
||
</th>
|
||
<th style="min-width: 150px;">标题</th>
|
||
<th style="min-width: 180px;">生产计划明细物料需求清单编号</th>
|
||
<th style="min-width: 150px;">生产计划编号</th>
|
||
<th style="min-width: 150px;">产品BOM分析结果</th>
|
||
<th style="min-width: 100px;">状态</th>
|
||
<th style="min-width: 120px;">需求状态</th>
|
||
<th style="min-width: 120px;">物料齐套率</th>
|
||
<th style="min-width: 120px;">物料编码</th>
|
||
<th style="min-width: 150px;">物料名称</th>
|
||
<th style="min-width: 120px;">物料批次号</th>
|
||
<th style="min-width: 100px;">物料层级</th>
|
||
<th style="min-width: 120px;">所需物料数</th>
|
||
<th style="min-width: 140px;">库存现有物料数</th>
|
||
<th style="min-width: 100px;">欠缺值</th>
|
||
<th style="min-width: 140px;">物料获取方式</th>
|
||
<th style="min-width: 120px;">实时库存值</th>
|
||
<th style="min-width: 120px;">待入库数量</th>
|
||
<th style="min-width: 120px;">派发数量</th>
|
||
<th style="min-width: 120px;">入库数量</th>
|
||
<th style="min-width: 100px;">提交人</th>
|
||
<th style="min-width: 160px;">提交时间</th>
|
||
<th style="min-width: 160px;">更新时间</th>
|
||
<th style="min-width: 120px;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="material-tbody">
|
||
<tr>
|
||
<td colspan="24" style="text-align: center; padding: 40px;">
|
||
<div style="color: var(--text-2);">暂无数据</div>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="pagination" id="pagination">
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.prevPage()" id="prev-btn" disabled>上一页</button>
|
||
<span id="page-info">第 1 页 / 共 1 页</span>
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.nextPage()" id="next-btn" disabled>下一页</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 导出对话框 -->
|
||
<div id="export-dialog" class="modal" style="display: none;">
|
||
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
|
||
<div class="modal-header">
|
||
<h3>选择导出字段</h3>
|
||
<button class="modal-close" onclick="MaterialPurchase.closeExportDialog()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div style="margin-bottom: 16px; display: flex; gap: 12px;">
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.selectAllFields()">全选</button>
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.deselectAllFields()">取消全选</button>
|
||
</div>
|
||
<div class="export-fields-grid">
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="title" data-label="标题" /><span class="field-checkmark"></span>
|
||
<span>标题</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="listNo" data-label="生产计划明细物料需求清单编号" /><span class="field-checkmark"></span>
|
||
<span>生产计划明细物料需求清单编号</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="planNo" data-label="生产计划编号" /><span class="field-checkmark"></span>
|
||
<span>生产计划编号</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="bomResult" data-label="产品BOM分析结果" /><span class="field-checkmark"></span>
|
||
<span>产品BOM分析结果</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="status" data-label="状态" /><span class="field-checkmark"></span>
|
||
<span>状态</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="demandStatus" data-label="需求状态" /><span class="field-checkmark"></span>
|
||
<span>需求状态</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="completeRate" data-label="物料齐套率" /><span class="field-checkmark"></span>
|
||
<span>物料齐套率</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="materialCode" data-label="物料编码" /><span class="field-checkmark"></span>
|
||
<span>物料编码</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="materialName" data-label="物料名称" /><span class="field-checkmark"></span>
|
||
<span>物料名称</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="batchNo" data-label="物料批次号" /><span class="field-checkmark"></span>
|
||
<span>物料批次号</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="level" data-label="物料层级" /><span class="field-checkmark"></span>
|
||
<span>物料层级</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="requiredQty" data-label="所需物料数" /><span class="field-checkmark"></span>
|
||
<span>所需物料数</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="stockQty" data-label="库存现有物料数" /><span class="field-checkmark"></span>
|
||
<span>库存现有物料数</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="shortage" data-label="欠缺值" /><span class="field-checkmark"></span>
|
||
<span>欠缺值</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="acquireMethod" data-label="物料获取方式" /><span class="field-checkmark"></span>
|
||
<span>物料获取方式</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="realtimeStock" data-label="实时库存值" /><span class="field-checkmark"></span>
|
||
<span>实时库存值</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="pendingQty" data-label="待入库数量" /><span class="field-checkmark"></span>
|
||
<span>待入库数量</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="dispatchedQty" data-label="派发数量" /><span class="field-checkmark"></span>
|
||
<span>派发数量</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="receivedQty" data-label="入库数量" /><span class="field-checkmark"></span>
|
||
<span>入库数量</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="submitter" data-label="提交人" /><span class="field-checkmark"></span>
|
||
<span>提交人</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="submitTime" data-label="提交时间" /><span class="field-checkmark"></span>
|
||
<span>提交时间</span>
|
||
</label>
|
||
<label class="export-field-item">
|
||
<input type="checkbox" class="export-field-checkbox" value="updateTime" data-label="更新时间" /><span class="field-checkmark"></span>
|
||
<span>更新时间</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.closeExportDialog()">取消</button>
|
||
<button class="btn btn-primary" onclick="MaterialPurchase.performExport()">
|
||
<span>📤</span> 导出Excel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新增/编辑对话框 -->
|
||
<div id="material-dialog" class="modal" style="display: none;">
|
||
<div class="modal-content" style="max-width: 800px; max-height: 90vh; overflow-y: auto;">
|
||
<div class="modal-header">
|
||
<h3 id="dialog-title">新增物料需求</h3>
|
||
<button class="modal-close" onclick="MaterialPurchase.closeDialog()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="material-form">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>标题 <span class="required">*</span></label>
|
||
<input type="text" id="form-title" class="input-field" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>生产计划明细物料需求清单编号 <span class="required">*</span></label>
|
||
<input type="text" id="form-list-no" class="input-field" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>生产计划编号 <span class="required">*</span></label>
|
||
<input type="text" id="form-plan-no" class="input-field" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>产品BOM分析结果</label>
|
||
<input type="text" id="form-bom-result" class="input-field" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>状态 <span class="required">*</span></label>
|
||
<select id="form-status" class="input-field" required>
|
||
<option value="pending">待处理</option>
|
||
<option value="processing">处理中</option>
|
||
<option value="completed">已完成</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>需求状态 <span class="required">*</span></label>
|
||
<select id="form-demand-status" class="input-field" required>
|
||
<option value="urgent">紧急</option>
|
||
<option value="normal">正常</option>
|
||
<option value="low">低优先级</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>物料齐套率 (%)</label>
|
||
<input type="number" id="form-complete-rate" class="input-field" min="0" max="100" step="0.01" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>物料编码 <span class="required">*</span></label>
|
||
<input type="text" id="form-material-code" class="input-field" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>物料名称 <span class="required">*</span></label>
|
||
<input type="text" id="form-material-name" class="input-field" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>物料批次号</label>
|
||
<input type="text" id="form-batch-no" class="input-field" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>物料层级</label>
|
||
<input type="number" id="form-level" class="input-field" min="1" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>所需物料数 <span class="required">*</span></label>
|
||
<input type="number" id="form-required-qty" class="input-field" min="0" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>库存现有物料数</label>
|
||
<input type="number" id="form-stock-qty" class="input-field" min="0" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>欠缺值</label>
|
||
<input type="number" id="form-shortage" class="input-field" readonly />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>物料获取方式</label>
|
||
<select id="form-acquire-method" class="input-field">
|
||
<option value="">请选择</option>
|
||
<option value="purchase">采购</option>
|
||
<option value="production">生产</option>
|
||
<option value="transfer">调拨</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>实时库存值</label>
|
||
<input type="number" id="form-realtime-stock" class="input-field" min="0" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>待入库数量</label>
|
||
<input type="number" id="form-pending-qty" class="input-field" min="0" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>派发数量</label>
|
||
<input type="number" id="form-dispatched-qty" class="input-field" min="0" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>入库数量</label>
|
||
<input type="number" id="form-received-qty" class="input-field" min="0" />
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.closeDialog()">取消</button>
|
||
<button class="btn btn-primary" onclick="MaterialPurchase.saveData()">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 回收站对话框 -->
|
||
<div id="recycle-bin-dialog" class="modal" style="display: none;">
|
||
<div class="modal-content" style="max-width: 1200px; max-height: 90vh; overflow-y: auto;">
|
||
<div class="modal-header">
|
||
<h3>🗑️ 回收站</h3>
|
||
<button class="modal-close" onclick="MaterialPurchase.closeRecycleBin()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div style="margin-bottom: 16px; padding: 12px; background: rgba(245, 158, 11, 0.1); border-left: 3px solid #f59e0b; border-radius: 6px; color: var(--text-2); font-size: 13px;">
|
||
<strong>提示:</strong>回收站中的数据将保留30天,过期后将自动永久删除
|
||
</div>
|
||
<div class="table-container" style="overflow-x: auto; max-height: 500px;">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="min-width: 150px;">标题</th>
|
||
<th style="min-width: 180px;">物料需求清单编号</th>
|
||
<th style="min-width: 120px;">物料编码</th>
|
||
<th style="min-width: 150px;">物料名称</th>
|
||
<th style="min-width: 160px;">删除时间</th>
|
||
<th style="min-width: 120px;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="recycle-bin-tbody">
|
||
<tr>
|
||
<td colspan="6" style="text-align: center; padding: 40px;">
|
||
<div style="color: var(--text-2);">回收站为空</div>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-danger" onclick="MaterialPurchase.emptyRecycleBin()">
|
||
<span>🗑️</span> 清空回收站
|
||
</button>
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.closeRecycleBin()">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 导入对话框 -->
|
||
<div id="import-dialog" class="modal" style="display: none;">
|
||
<div class="modal-content" style="max-width: 600px;">
|
||
<div class="modal-header">
|
||
<h3>📥 从Excel导入物料清单</h3>
|
||
<button class="modal-close" onclick="MaterialPurchase.closeImportDialog()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div style="margin-bottom: 16px; padding: 12px; background: rgba(79, 140, 255, 0.1); border-left: 3px solid var(--primary); border-radius: 6px; color: var(--text-2); font-size: 13px;">
|
||
<strong>Excel文件要求:</strong><br/>
|
||
• 必须包含以下列:标题、生产计划明细物料需求清单编号、生产计划编号、状态、需求状态、物料编码、物料名称、所需物料数<br/>
|
||
• 可选列:产品BOM分析结果、物料齐套率、物料批次号、物料层级、库存现有物料数、欠缺值、物料获取方式、实时库存值、待入库数量、派发数量、入库数量<br/>
|
||
• 第一行必须是表头
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>选择Excel文件</label>
|
||
<input type="file" id="import-file-input" accept=".xlsx,.xls" class="input-field" />
|
||
</div>
|
||
|
||
<div id="import-status" style="margin-top: 16px; padding: 12px; border-radius: 6px; display: none;"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" onclick="MaterialPurchase.closeImportDialog()">取消</button>
|
||
<button class="btn btn-primary" onclick="MaterialPurchase.validateImportFile()">
|
||
<span>✓</span> 验证文件
|
||
</button>
|
||
<button class="btn btn-primary" id="import-confirm-btn" onclick="MaterialPurchase.confirmImport()" style="display: none;">
|
||
<span>📥</span> 确认导入
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
window.MaterialPurchase = {
|
||
currentPage: 1,
|
||
pageSize: 20,
|
||
totalPages: 1,
|
||
data: [],
|
||
editingId: null,
|
||
selectedIds: new Set(),
|
||
recycleBin: [],
|
||
|
||
init() {
|
||
this.loadFromStorage();
|
||
this.loadData();
|
||
this.bindEvents();
|
||
},
|
||
|
||
// 从 localStorage 加载数据
|
||
loadFromStorage() {
|
||
try {
|
||
const savedData = localStorage.getItem('materialPurchaseData');
|
||
const savedRecycleBin = localStorage.getItem('materialPurchaseRecycleBin');
|
||
|
||
if (savedData) {
|
||
this.data = JSON.parse(savedData);
|
||
}
|
||
if (savedRecycleBin) {
|
||
this.recycleBin = JSON.parse(savedRecycleBin);
|
||
}
|
||
} catch (error) {
|
||
console.error('加载本地数据失败:', error);
|
||
}
|
||
},
|
||
|
||
// 保存到 localStorage
|
||
saveToStorage() {
|
||
try {
|
||
localStorage.setItem('materialPurchaseData', JSON.stringify(this.data));
|
||
localStorage.setItem('materialPurchaseRecycleBin', JSON.stringify(this.recycleBin));
|
||
} catch (error) {
|
||
console.error('保存本地数据失败:', error);
|
||
}
|
||
},
|
||
|
||
bindEvents() {
|
||
// 自动计算欠缺值
|
||
const requiredQty = document.getElementById('form-required-qty');
|
||
const stockQty = document.getElementById('form-stock-qty');
|
||
const shortage = document.getElementById('form-shortage');
|
||
|
||
const calculateShortage = () => {
|
||
const required = parseFloat(requiredQty?.value || 0);
|
||
const stock = parseFloat(stockQty?.value || 0);
|
||
if (shortage) {
|
||
shortage.value = Math.max(0, required - stock);
|
||
}
|
||
};
|
||
|
||
requiredQty?.addEventListener('input', calculateShortage);
|
||
stockQty?.addEventListener('input', calculateShortage);
|
||
},
|
||
|
||
async loadData() {
|
||
try {
|
||
const response = await fetch('/api/material-purchase/list', {
|
||
credentials: 'include'
|
||
});
|
||
const result = await response.json();
|
||
this.data = result.list || [];
|
||
|
||
// 加载回收站数据
|
||
const recycleBinResponse = await fetch('/api/material-purchase/recycle-bin', {
|
||
credentials: 'include'
|
||
});
|
||
const recycleBinResult = await recycleBinResponse.json();
|
||
this.recycleBin = recycleBinResult.list || [];
|
||
|
||
this.renderTable();
|
||
this.updatePagination();
|
||
} catch (error) {
|
||
API.toast('加载数据失败: ' + error.message, 'error');
|
||
}
|
||
},
|
||
|
||
generateMockData() {
|
||
// 生成模拟数据
|
||
const mockData = [];
|
||
for (let i = 1; i <= 5; i++) {
|
||
mockData.push({
|
||
id: i,
|
||
title: `物料需求计划-${i}`,
|
||
listNo: `MRL-2024-${String(i).padStart(4, '0')}`,
|
||
planNo: `PLAN-2024-${String(i).padStart(4, '0')}`,
|
||
bomResult: `BOM分析-${i}`,
|
||
status: ['pending', 'processing', 'completed'][i % 3],
|
||
demandStatus: ['urgent', 'normal', 'low'][i % 3],
|
||
completeRate: (Math.random() * 100).toFixed(2),
|
||
materialCode: `MAT-${String(i).padStart(5, '0')}`,
|
||
materialName: `物料名称-${i}`,
|
||
batchNo: `BATCH-${String(i).padStart(4, '0')}`,
|
||
level: Math.floor(Math.random() * 3) + 1,
|
||
requiredQty: Math.floor(Math.random() * 1000) + 100,
|
||
stockQty: Math.floor(Math.random() * 500),
|
||
shortage: 0,
|
||
acquireMethod: ['purchase', 'production', 'transfer'][i % 3],
|
||
realtimeStock: Math.floor(Math.random() * 500),
|
||
pendingQty: Math.floor(Math.random() * 200),
|
||
dispatchedQty: Math.floor(Math.random() * 100),
|
||
receivedQty: Math.floor(Math.random() * 150),
|
||
submitter: `用户${i}`,
|
||
submitTime: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||
updateTime: new Date().toISOString()
|
||
});
|
||
mockData[i - 1].shortage = Math.max(0, mockData[i - 1].requiredQty - mockData[i - 1].stockQty);
|
||
}
|
||
return mockData;
|
||
},
|
||
|
||
renderTable() {
|
||
const tbody = document.getElementById('material-tbody');
|
||
if (!tbody) return;
|
||
|
||
if (this.data.length === 0) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="24" style="text-align: center; padding: 40px;">
|
||
<div style="color: var(--text-2);">暂无数据</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
const start = (this.currentPage - 1) * this.pageSize;
|
||
const end = start + this.pageSize;
|
||
const pageData = this.data.slice(start, end);
|
||
|
||
tbody.innerHTML = pageData.map(item => `
|
||
<tr>
|
||
<td style="text-align: center;">
|
||
<label class="custom-checkbox"><input type="checkbox" class="row-checkbox" value="${item.id}"
|
||
${this.selectedIds.has(item.id) ? 'checked' : ''}
|
||
onchange="MaterialPurchase.toggleRowSelection(${item.id}, this.checked)" /><span class="checkmark"></span></label>
|
||
</td>
|
||
<td>${this.escapeHtml(item.title)}</td>
|
||
<td>${this.escapeHtml(item.listNo)}</td>
|
||
<td>${this.escapeHtml(item.planNo)}</td>
|
||
<td>${this.escapeHtml(item.bomResult)}</td>
|
||
<td><span class="badge badge-${this.getStatusClass(item.status)}">${this.getStatusText(item.status)}</span></td>
|
||
<td><span class="badge badge-${this.getDemandStatusClass(item.demandStatus)}">${this.getDemandStatusText(item.demandStatus)}</span></td>
|
||
<td>${item.completeRate}%</td>
|
||
<td>${this.escapeHtml(item.materialCode)}</td>
|
||
<td>${this.escapeHtml(item.materialName)}</td>
|
||
<td>${this.escapeHtml(item.batchNo)}</td>
|
||
<td>${item.level}</td>
|
||
<td>${item.requiredQty}</td>
|
||
<td>${item.stockQty}</td>
|
||
<td>${item.shortage}</td>
|
||
<td>${this.getAcquireMethodText(item.acquireMethod)}</td>
|
||
<td>${item.realtimeStock}</td>
|
||
<td>${item.pendingQty}</td>
|
||
<td>${item.dispatchedQty}</td>
|
||
<td>${item.receivedQty}</td>
|
||
<td>${this.escapeHtml(item.submitter)}</td>
|
||
<td>${this.formatDateTime(item.submitTime)}</td>
|
||
<td>${this.formatDateTime(item.updateTime)}</td>
|
||
<td>
|
||
<button class="btn-icon" onclick="MaterialPurchase.editItem(${item.id})" title="编辑">✏️</button>
|
||
<button class="btn-icon" onclick="MaterialPurchase.deleteItem(${item.id})" title="删除">🗑️</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
|
||
// 更新全选框状态
|
||
this.updateSelectAllCheckbox();
|
||
},
|
||
|
||
updatePagination() {
|
||
this.totalPages = Math.ceil(this.data.length / this.pageSize);
|
||
const pageInfo = document.getElementById('page-info');
|
||
const prevBtn = document.getElementById('prev-btn');
|
||
const nextBtn = document.getElementById('next-btn');
|
||
|
||
if (pageInfo) {
|
||
pageInfo.textContent = `第 ${this.currentPage} 页 / 共 ${this.totalPages} 页`;
|
||
}
|
||
if (prevBtn) {
|
||
prevBtn.disabled = this.currentPage === 1;
|
||
}
|
||
if (nextBtn) {
|
||
nextBtn.disabled = this.currentPage === this.totalPages || this.totalPages === 0;
|
||
}
|
||
},
|
||
|
||
prevPage() {
|
||
if (this.currentPage > 1) {
|
||
this.currentPage--;
|
||
this.renderTable();
|
||
this.updatePagination();
|
||
}
|
||
},
|
||
|
||
nextPage() {
|
||
if (this.currentPage < this.totalPages) {
|
||
this.currentPage++;
|
||
this.renderTable();
|
||
this.updatePagination();
|
||
}
|
||
},
|
||
|
||
search() {
|
||
const keyword = document.getElementById('search-keyword')?.value.toLowerCase() || '';
|
||
const status = document.getElementById('filter-status')?.value || '';
|
||
const demandStatus = document.getElementById('filter-demand-status')?.value || '';
|
||
|
||
this.data = this.generateMockData().filter(item => {
|
||
const matchKeyword = !keyword ||
|
||
item.title.toLowerCase().includes(keyword) ||
|
||
item.listNo.toLowerCase().includes(keyword) ||
|
||
item.planNo.toLowerCase().includes(keyword) ||
|
||
item.materialCode.toLowerCase().includes(keyword);
|
||
|
||
const matchStatus = !status || item.status === status;
|
||
const matchDemandStatus = !demandStatus || item.demandStatus === demandStatus;
|
||
|
||
return matchKeyword && matchStatus && matchDemandStatus;
|
||
});
|
||
|
||
this.currentPage = 1;
|
||
this.renderTable();
|
||
this.updatePagination();
|
||
},
|
||
|
||
resetFilter() {
|
||
document.getElementById('search-keyword').value = '';
|
||
document.getElementById('filter-status').value = '';
|
||
document.getElementById('filter-demand-status').value = '';
|
||
this.loadData();
|
||
},
|
||
|
||
showAddDialog() {
|
||
this.editingId = null;
|
||
document.getElementById('dialog-title').textContent = '新增物料需求';
|
||
document.getElementById('material-form').reset();
|
||
document.getElementById('material-dialog').style.display = 'flex';
|
||
},
|
||
|
||
editItem(id) {
|
||
this.editingId = id;
|
||
const item = this.data.find(d => d.id === id);
|
||
if (!item) return;
|
||
|
||
document.getElementById('dialog-title').textContent = '编辑物料需求';
|
||
document.getElementById('form-title').value = item.title;
|
||
document.getElementById('form-list-no').value = item.listNo;
|
||
document.getElementById('form-plan-no').value = item.planNo;
|
||
document.getElementById('form-bom-result').value = item.bomResult;
|
||
document.getElementById('form-status').value = item.status;
|
||
document.getElementById('form-demand-status').value = item.demandStatus;
|
||
document.getElementById('form-complete-rate').value = item.completeRate;
|
||
document.getElementById('form-material-code').value = item.materialCode;
|
||
document.getElementById('form-material-name').value = item.materialName;
|
||
document.getElementById('form-batch-no').value = item.batchNo;
|
||
document.getElementById('form-level').value = item.level;
|
||
document.getElementById('form-required-qty').value = item.requiredQty;
|
||
document.getElementById('form-stock-qty').value = item.stockQty;
|
||
document.getElementById('form-shortage').value = item.shortage;
|
||
document.getElementById('form-acquire-method').value = item.acquireMethod;
|
||
document.getElementById('form-realtime-stock').value = item.realtimeStock;
|
||
document.getElementById('form-pending-qty').value = item.pendingQty;
|
||
document.getElementById('form-dispatched-qty').value = item.dispatchedQty;
|
||
document.getElementById('form-received-qty').value = item.receivedQty;
|
||
|
||
document.getElementById('material-dialog').style.display = 'flex';
|
||
},
|
||
|
||
closeDialog() {
|
||
document.getElementById('material-dialog').style.display = 'none';
|
||
this.editingId = null;
|
||
},
|
||
|
||
async saveData() {
|
||
const form = document.getElementById('material-form');
|
||
if (!form.checkValidity()) {
|
||
form.reportValidity();
|
||
return;
|
||
}
|
||
|
||
const data = {
|
||
title: document.getElementById('form-title').value,
|
||
list_no: document.getElementById('form-list-no').value,
|
||
plan_no: document.getElementById('form-plan-no').value,
|
||
bom_result: document.getElementById('form-bom-result').value,
|
||
status: document.getElementById('form-status').value,
|
||
demand_status: document.getElementById('form-demand-status').value,
|
||
complete_rate: parseFloat(document.getElementById('form-complete-rate').value) || 0,
|
||
material_code: document.getElementById('form-material-code').value,
|
||
material_name: document.getElementById('form-material-name').value,
|
||
batch_no: document.getElementById('form-batch-no').value,
|
||
level: parseInt(document.getElementById('form-level').value) || 1,
|
||
required_qty: parseInt(document.getElementById('form-required-qty').value) || 0,
|
||
stock_qty: parseInt(document.getElementById('form-stock-qty').value) || 0,
|
||
shortage: parseInt(document.getElementById('form-shortage').value) || 0,
|
||
acquire_method: document.getElementById('form-acquire-method').value,
|
||
realtime_stock: parseInt(document.getElementById('form-realtime-stock').value) || 0,
|
||
pending_qty: parseInt(document.getElementById('form-pending-qty').value) || 0,
|
||
dispatched_qty: parseInt(document.getElementById('form-dispatched-qty').value) || 0,
|
||
received_qty: parseInt(document.getElementById('form-received-qty').value) || 0
|
||
};
|
||
|
||
try {
|
||
const url = this.editingId ? '/api/material-purchase/update' : '/api/material-purchase/add';
|
||
if (this.editingId) {
|
||
data.id = this.editingId;
|
||
}
|
||
|
||
const response = await fetch(url, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
credentials: 'include',
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.ok) {
|
||
API.toast(this.editingId ? '更新成功' : '添加成功', 'success');
|
||
this.closeDialog();
|
||
await this.loadData();
|
||
} else {
|
||
API.toast(result.error || '保存失败', 'error');
|
||
}
|
||
} catch (error) {
|
||
API.toast('保存失败: ' + error.message, 'error');
|
||
}
|
||
},
|
||
|
||
async deleteItem(id) {
|
||
if (!confirm('确定要删除这条记录吗?\n\n删除的数据将移至回收站,可在回收站中找回。')) return;
|
||
|
||
try {
|
||
const response = await fetch('/api/material-purchase/delete', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
credentials: 'include',
|
||
body: JSON.stringify({ ids: [id] })
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.ok) {
|
||
API.toast('已删除,可在回收站中找回', 'success');
|
||
await this.loadData();
|
||
} else {
|
||
API.toast(result.error || '删除失败', 'error');
|
||
}
|
||
} catch (error) {
|
||
API.toast('删除失败: ' + error.message, 'error');
|
||
}
|
||
},
|
||
|
||
exportData() {
|
||
this.showExportDialog();
|
||
},
|
||
|
||
showExportDialog() {
|
||
const dialog = document.getElementById('export-dialog');
|
||
if (dialog) {
|
||
dialog.style.display = 'flex';
|
||
// 默认全选
|
||
document.querySelectorAll('.export-field-checkbox').forEach(cb => {
|
||
cb.checked = true;
|
||
});
|
||
}
|
||
},
|
||
|
||
closeExportDialog() {
|
||
const dialog = document.getElementById('export-dialog');
|
||
if (dialog) {
|
||
dialog.style.display = 'none';
|
||
}
|
||
},
|
||
|
||
async performExport() {
|
||
// 获取选中的字段
|
||
const selectedFields = [];
|
||
document.querySelectorAll('.export-field-checkbox:checked').forEach(cb => {
|
||
selectedFields.push({
|
||
key: cb.value,
|
||
label: cb.dataset.label
|
||
});
|
||
});
|
||
|
||
if (selectedFields.length === 0) {
|
||
API.toast('请至少选择一个导出字段', 'warning');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 动态加载 SheetJS 库
|
||
if (!window.XLSX) {
|
||
API.toast('正在加载导出库...', 'info');
|
||
await this.loadSheetJS();
|
||
}
|
||
|
||
// 准备导出数据
|
||
const exportData = this.data.map(item => {
|
||
const row = {};
|
||
selectedFields.forEach(field => {
|
||
let value = item[field.key];
|
||
|
||
// 格式化特殊字段
|
||
if (field.key === 'status') {
|
||
value = this.getStatusText(value);
|
||
} else if (field.key === 'demandStatus') {
|
||
value = this.getDemandStatusText(value);
|
||
} else if (field.key === 'acquireMethod') {
|
||
value = this.getAcquireMethodText(value);
|
||
} else if (field.key === 'submitTime' || field.key === 'updateTime') {
|
||
value = this.formatDateTime(value);
|
||
} else if (field.key === 'completeRate') {
|
||
value = value + '%';
|
||
}
|
||
|
||
row[field.label] = value || '';
|
||
});
|
||
return row;
|
||
});
|
||
|
||
// 创建工作簿
|
||
const ws = window.XLSX.utils.json_to_sheet(exportData);
|
||
const wb = window.XLSX.utils.book_new();
|
||
window.XLSX.utils.book_append_sheet(wb, ws, '物料清单');
|
||
|
||
// 设置列宽
|
||
const colWidths = selectedFields.map(field => {
|
||
let width = 15;
|
||
if (field.key === 'listNo' || field.key === 'planNo') width = 20;
|
||
if (field.key === 'title' || field.key === 'materialName') width = 25;
|
||
if (field.key === 'submitTime' || field.key === 'updateTime') width = 20;
|
||
return { wch: width };
|
||
});
|
||
ws['!cols'] = colWidths;
|
||
|
||
// 生成文件名
|
||
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
|
||
const filename = `物料清单-采购_${timestamp}.xlsx`;
|
||
|
||
// 导出文件
|
||
window.XLSX.writeFile(wb, filename);
|
||
|
||
API.toast('导出成功!', 'success');
|
||
this.closeExportDialog();
|
||
} catch (error) {
|
||
console.error('导出失败:', error);
|
||
API.toast('导出失败: ' + error.message, 'error');
|
||
}
|
||
},
|
||
|
||
async loadSheetJS() {
|
||
return new Promise((resolve, reject) => {
|
||
const script = document.createElement('script');
|
||
script.src = 'https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js';
|
||
script.onload = resolve;
|
||
script.onerror = () => reject(new Error('加载导出库失败'));
|
||
document.head.appendChild(script);
|
||
});
|
||
},
|
||
|
||
selectAllFields() {
|
||
document.querySelectorAll('.export-field-checkbox').forEach(cb => {
|
||
cb.checked = true;
|
||
});
|
||
},
|
||
|
||
deselectAllFields() {
|
||
document.querySelectorAll('.export-field-checkbox').forEach(cb => {
|
||
cb.checked = false;
|
||
});
|
||
},
|
||
|
||
toggleRowSelection(id, checked) {
|
||
if (checked) {
|
||
this.selectedIds.add(id);
|
||
} else {
|
||
this.selectedIds.delete(id);
|
||
}
|
||
this.updateSelectAllCheckbox();
|
||
this.updateSelectionInfo();
|
||
},
|
||
|
||
toggleSelectAll(checkbox) {
|
||
const checkboxes = document.querySelectorAll('.row-checkbox');
|
||
checkboxes.forEach(cb => {
|
||
const id = parseInt(cb.value);
|
||
cb.checked = checkbox.checked;
|
||
if (checkbox.checked) {
|
||
this.selectedIds.add(id);
|
||
} else {
|
||
this.selectedIds.delete(id);
|
||
}
|
||
});
|
||
this.updateSelectionInfo();
|
||
},
|
||
|
||
updateSelectAllCheckbox() {
|
||
const selectAllCheckbox = document.getElementById('select-all-checkbox');
|
||
const checkboxes = document.querySelectorAll('.row-checkbox');
|
||
const checkedCount = document.querySelectorAll('.row-checkbox:checked').length;
|
||
|
||
if (selectAllCheckbox) {
|
||
selectAllCheckbox.checked = checkboxes.length > 0 && checkedCount === checkboxes.length;
|
||
selectAllCheckbox.indeterminate = checkedCount > 0 && checkedCount < checkboxes.length;
|
||
}
|
||
},
|
||
|
||
updateSelectionInfo() {
|
||
const count = this.selectedIds.size;
|
||
const infoEl = document.getElementById('selection-info');
|
||
if (infoEl) {
|
||
if (count > 0) {
|
||
infoEl.textContent = `已选择 ${count} 项`;
|
||
infoEl.style.display = 'inline-block';
|
||
} else {
|
||
infoEl.style.display = 'none';
|
||
}
|
||
}
|
||
},
|
||
|
||
clearSelection() {
|
||
this.selectedIds.clear();
|
||
document.querySelectorAll('.row-checkbox').forEach(cb => {
|
||
cb.checked = false;
|
||
});
|
||
const selectAllCheckbox = document.getElementById('select-all-checkbox');
|
||
if (selectAllCheckbox) {
|
||
selectAllCheckbox.checked = false;
|
||
selectAllCheckbox.indeterminate = false;
|
||
}
|
||
this.updateSelectionInfo();
|
||
},
|
||
|
||
async deleteSelected() {
|
||
if (this.selectedIds.size === 0) {
|
||
API.toast('请先选择要删除的项', 'warning');
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`确定要删除选中的 ${this.selectedIds.size} 项吗?\n\n删除的数据将移至回收站,可在回收站中找回。`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const ids = Array.from(this.selectedIds);
|
||
const response = await fetch('/api/material-purchase/delete', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
credentials: 'include',
|
||
body: JSON.stringify({ ids })
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.ok) {
|
||
API.toast(`已删除 ${result.count} 项,可在回收站中找回`, 'success');
|
||
this.selectedIds.clear();
|
||
await this.loadData();
|
||
this.updateSelectionInfo();
|
||
} else {
|
||
API.toast(result.error || '删除失败', 'error');
|
||
}
|
||
} catch (error) {
|
||
API.toast('删除失败: ' + error.message, 'error');
|
||
}
|
||
},
|
||
|
||
exportSelected() {
|
||
if (this.selectedIds.size === 0) {
|
||
API.toast('请先选择要导出的项', 'warning');
|
||
return;
|
||
}
|
||
|
||
// 临时保存原始数据
|
||
const originalData = this.data;
|
||
// 只导出选中的数据
|
||
this.data = this.data.filter(item => this.selectedIds.has(item.id));
|
||
|
||
// 显示导出对话框
|
||
this.showExportDialog();
|
||
|
||
// 恢复原始数据
|
||
setTimeout(() => {
|
||
this.data = originalData;
|
||
}, 100);
|
||
},
|
||
|
||
getStatusClass(status) {
|
||
const map = {
|
||
pending: 'warning',
|
||
processing: 'info',
|
||
completed: 'success'
|
||
};
|
||
return map[status] || 'default';
|
||
},
|
||
|
||
getStatusText(status) {
|
||
const map = {
|
||
pending: '待处理',
|
||
processing: '处理中',
|
||
completed: '已完成'
|
||
};
|
||
return map[status] || status;
|
||
},
|
||
|
||
getDemandStatusClass(status) {
|
||
const map = {
|
||
urgent: 'danger',
|
||
normal: 'info',
|
||
low: 'default'
|
||
};
|
||
return map[status] || 'default';
|
||
},
|
||
|
||
getDemandStatusText(status) {
|
||
const map = {
|
||
urgent: '紧急',
|
||
normal: '正常',
|
||
low: '低优先级'
|
||
};
|
||
return map[status] || status;
|
||
},
|
||
|
||
getAcquireMethodText(method) {
|
||
if (!method) return '-';
|
||
|
||
const map = {
|
||
purchase: '采购',
|
||
production: '生产',
|
||
transfer: '调拨',
|
||
// 支持中文输入
|
||
'采购': '采购',
|
||
'生产': '生产',
|
||
'调拨': '调拨'
|
||
};
|
||
return map[method] || method || '-';
|
||
},
|
||
|
||
formatDateTime(dateStr) {
|
||
if (!dateStr) return '-';
|
||
const date = new Date(dateStr);
|
||
return date.toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
},
|
||
|
||
escapeHtml(text) {
|
||
if (!text) return '';
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
},
|
||
|
||
// 回收站相关功能
|
||
showRecycleBin() {
|
||
const dialog = document.getElementById('recycle-bin-dialog');
|
||
if (dialog) {
|
||
this.renderRecycleBin();
|
||
dialog.style.display = 'flex';
|
||
}
|
||
},
|
||
|
||
closeRecycleBin() {
|
||
const dialog = document.getElementById('recycle-bin-dialog');
|
||
if (dialog) {
|
||
dialog.style.display = 'none';
|
||
}
|
||
},
|
||
|
||
renderRecycleBin() {
|
||
const tbody = document.getElementById('recycle-bin-tbody');
|
||
if (!tbody) return;
|
||
|
||
if (this.recycleBin.length === 0) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="6" style="text-align: center; padding: 40px;">
|
||
<div style="color: var(--text-2);">回收站为空</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = this.recycleBin.map(item => `
|
||
<tr>
|
||
<td>${this.escapeHtml(item.title)}</td>
|
||
<td>${this.escapeHtml(item.listNo)}</td>
|
||
<td>${this.escapeHtml(item.materialCode)}</td>
|
||
<td>${this.escapeHtml(item.materialName)}</td>
|
||
<td>${this.formatDateTime(item.deletedAt)}</td>
|
||
<td>
|
||
<button class="btn-icon" onclick="MaterialPurchase.restoreItem(${item.id})" title="恢复">
|
||
♻️
|
||
</button>
|
||
<button class="btn-icon" onclick="MaterialPurchase.permanentDelete(${item.id})" title="永久删除">
|
||
❌
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
},
|
||
|
||
async restoreItem(id) {
|
||
try {
|
||
const response = await fetch('/api/material-purchase/restore', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
credentials: 'include',
|
||
body: JSON.stringify({ id })
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.ok) {
|
||
API.toast('恢复成功', 'success');
|
||
await this.loadData();
|
||
this.renderRecycleBin();
|
||
} else {
|
||
API.toast(result.error || '恢复失败', 'error');
|
||
}
|
||
} catch (error) {
|
||
API.toast('恢复失败: ' + error.message, 'error');
|
||
}
|
||
},
|
||
|
||
async permanentDelete(id) {
|
||
if (!confirm('确定要永久删除这条记录吗?此操作不可恢复!')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/material-purchase/permanent-delete', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
credentials: 'include',
|
||
body: JSON.stringify({ id })
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.ok) {
|
||
API.toast('已永久删除', 'success');
|
||
await this.loadData();
|
||
this.renderRecycleBin();
|
||
} else {
|
||
API.toast(result.error || '删除失败', 'error');
|
||
}
|
||
} catch (error) {
|
||
API.toast('删除失败: ' + error.message, 'error');
|
||
}
|
||
},
|
||
|
||
async emptyRecycleBin() {
|
||
if (this.recycleBin.length === 0) {
|
||
API.toast('回收站已经是空的', 'info');
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`确定要清空回收站吗?这将永久删除 ${this.recycleBin.length} 项数据,此操作不可恢复!`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/material-purchase/empty-recycle-bin', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
credentials: 'include'
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.ok) {
|
||
API.toast(`回收站已清空,删除了 ${result.count} 项`, 'success');
|
||
await this.loadData();
|
||
this.renderRecycleBin();
|
||
} else {
|
||
API.toast(result.error || '清空失败', 'error');
|
||
}
|
||
} catch (error) {
|
||
API.toast('清空失败: ' + error.message, 'error');
|
||
}
|
||
},
|
||
|
||
// 导入相关功能
|
||
showImportDialog() {
|
||
const dialog = document.getElementById('import-dialog');
|
||
if (dialog) {
|
||
// 重置状态
|
||
document.getElementById('import-file-input').value = '';
|
||
document.getElementById('import-status').style.display = 'none';
|
||
document.getElementById('import-confirm-btn').style.display = 'none';
|
||
dialog.style.display = 'flex';
|
||
}
|
||
},
|
||
|
||
closeImportDialog() {
|
||
const dialog = document.getElementById('import-dialog');
|
||
if (dialog) {
|
||
dialog.style.display = 'none';
|
||
}
|
||
},
|
||
|
||
async validateImportFile() {
|
||
const fileInput = document.getElementById('import-file-input');
|
||
const file = fileInput.files[0];
|
||
|
||
if (!file) {
|
||
API.toast('请选择要导入的文件', 'warning');
|
||
return;
|
||
}
|
||
|
||
const statusEl = document.getElementById('import-status');
|
||
const confirmBtn = document.getElementById('import-confirm-btn');
|
||
|
||
statusEl.style.display = 'block';
|
||
statusEl.style.background = 'rgba(79, 140, 255, 0.1)';
|
||
statusEl.style.color = 'var(--primary)';
|
||
statusEl.textContent = '正在验证文件...';
|
||
confirmBtn.style.display = 'none';
|
||
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
|
||
const response = await fetch('/api/validate/material-purchase-file', {
|
||
method: 'POST',
|
||
credentials: 'include',
|
||
body: formData
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.valid) {
|
||
statusEl.style.background = 'rgba(34, 197, 94, 0.1)';
|
||
statusEl.style.color = '#22c55e';
|
||
statusEl.textContent = '✓ ' + result.message;
|
||
confirmBtn.style.display = 'inline-block';
|
||
} else {
|
||
statusEl.style.background = 'rgba(239, 68, 68, 0.1)';
|
||
statusEl.style.color = '#ef4444';
|
||
statusEl.textContent = '✗ ' + result.message;
|
||
confirmBtn.style.display = 'none';
|
||
}
|
||
} catch (error) {
|
||
statusEl.style.background = 'rgba(239, 68, 68, 0.1)';
|
||
statusEl.style.color = '#ef4444';
|
||
statusEl.textContent = '✗ 验证失败: ' + error.message;
|
||
confirmBtn.style.display = 'none';
|
||
}
|
||
},
|
||
|
||
async confirmImport() {
|
||
const fileInput = document.getElementById('import-file-input');
|
||
const file = fileInput.files[0];
|
||
|
||
if (!file) {
|
||
API.toast('请选择要导入的文件', 'warning');
|
||
return;
|
||
}
|
||
|
||
const statusEl = document.getElementById('import-status');
|
||
const confirmBtn = document.getElementById('import-confirm-btn');
|
||
|
||
statusEl.style.display = 'block';
|
||
statusEl.style.background = 'rgba(79, 140, 255, 0.1)';
|
||
statusEl.style.color = 'var(--primary)';
|
||
statusEl.textContent = '正在导入数据...';
|
||
confirmBtn.disabled = true;
|
||
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
|
||
const response = await fetch('/api/upload/material-purchase-file', {
|
||
method: 'POST',
|
||
credentials: 'include',
|
||
body: formData
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.ok) {
|
||
statusEl.style.background = 'rgba(34, 197, 94, 0.1)';
|
||
statusEl.style.color = '#22c55e';
|
||
statusEl.textContent = '✓ ' + result.message;
|
||
|
||
API.toast(`成功导入 ${result.count} 条数据`, 'success');
|
||
|
||
// 延迟关闭对话框并刷新数据
|
||
setTimeout(async () => {
|
||
this.closeImportDialog();
|
||
await this.loadData();
|
||
}, 1500);
|
||
} else {
|
||
statusEl.style.background = 'rgba(239, 68, 68, 0.1)';
|
||
statusEl.style.color = '#ef4444';
|
||
statusEl.textContent = '✗ ' + (result.error || '导入失败');
|
||
confirmBtn.disabled = false;
|
||
}
|
||
} catch (error) {
|
||
statusEl.style.background = 'rgba(239, 68, 68, 0.1)';
|
||
statusEl.style.color = '#ef4444';
|
||
statusEl.textContent = '✗ 导入失败: ' + error.message;
|
||
confirmBtn.disabled = false;
|
||
}
|
||
}
|
||
};
|
||
|
||
// 页面加载后初始化
|
||
Router.onAfterEach((path) => {
|
||
if (path === '/plan-mgmt/material-purchase') {
|
||
setTimeout(() => MaterialPurchase.init(), 100);
|
||
}
|
||
});
|
||
})();
|