ERP/frontend/js/components/customer-order.js

420 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 客户订单管理
(() => {
Router.register('/plan-mgmt/customer-order', async () => {
// 先返回 HTML
const html = `
<div class="page-header">
<h1>客户订单管理</h1>
<div class="page-actions">
<button id="add-order-btn" class="btn btn-primary">新增订单</button>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>下单时间</th>
<th>订单编号</th>
<th>客户名称</th>
<th>物料</th>
<th>订单数量</th>
<th>单价</th>
<th>操作</th>
</tr>
</thead>
<tbody id="order-list">
<tr>
<td colspan="7" class="text-center">加载中...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 新增订单弹窗 -->
<div id="order-modal" class="modal" style="display:none;">
<div class="modal-content" style="max-width: 900px; max-height: 90vh; overflow-y: auto;">
<div class="modal-header">
<h2 id="modal-title">新增订单</h2>
<button class="modal-close" onclick="window.CustomerOrder.closeModal()">&times;</button>
</div>
<div class="modal-body">
<form id="order-form">
<!-- 订单基本信息 -->
<div style="background: var(--surface); padding: 20px; border-radius: 8px; margin-bottom: 20px; border: 1px solid var(--border);">
<h3 style="margin: 0 0 15px 0; font-size: 16px; color: var(--text); font-weight: 600;">订单基本信息</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px;">
<div class="field" style="margin: 0;">
<label>下单时间 <span style="color: var(--danger);">*</span></label>
<input type="date" id="order-date" class="input" required />
</div>
<div class="field" style="margin: 0;">
<label>订单编号 <span style="color: var(--danger);">*</span></label>
<input type="text" id="order-no" class="input" placeholder="如CGDD001695" required />
</div>
<div class="field" style="margin: 0;">
<label>客户名称 <span style="color: var(--danger);">*</span></label>
<input type="text" id="customer-name" class="input" placeholder="如:易泰勒" required />
</div>
</div>
</div>
<!-- 物料列表 -->
<div style="background: var(--surface); padding: 20px; border-radius: 8px; border: 1px solid var(--border);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h3 style="margin: 0; font-size: 16px; color: var(--text); font-weight: 600;">物料清单</h3>
<button type="button" class="btn btn-primary" onclick="window.CustomerOrder.addMaterialRow()">
添加物料
</button>
</div>
<div id="materials-container">
<!-- 物料行将动态添加到这里 -->
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="window.CustomerOrder.closeModal()">取消</button>
<button class="btn btn-primary" onclick="window.CustomerOrder.saveOrder()">保存订单</button>
</div>
</div>
</div>
`;
// DOM 渲染后初始化
setTimeout(() => {
const addBtn = document.getElementById('add-order-btn');
if (addBtn) {
addBtn.addEventListener('click', () => {
openModal();
});
}
loadOrders();
}, 100);
return html;
});
async function loadOrders() {
try {
console.log('开始加载订单列表...');
const res = await fetch('/api/customer-orders');
console.log('API响应状态:', res.status);
const data = await res.json();
console.log('订单数据:', data);
const tbody = document.getElementById('order-list');
if (!tbody) {
console.error('找不到 order-list 元素');
return;
}
if (!data.list || data.list.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" class="text-center">暂无数据</td></tr>';
return;
}
tbody.innerHTML = data.list.map(order => `
<tr>
<td>${order.order_date || '—'}</td>
<td>${order.order_no || '—'}</td>
<td>${order.customer_name || '—'}</td>
<td style="white-space: pre-wrap;">${order.material || '—'}</td>
<td>${order.quantity || 0}</td>
<td>${order.unit_price || 0}</td>
<td>
<button class="btn-text" onclick="window.CustomerOrder.editOrder(${order.id})">编辑</button>
<button class="btn-text btn-danger" onclick="window.CustomerOrder.deleteOrder(${order.id})">删除</button>
</td>
</tr>
`).join('');
console.log('订单列表加载完成');
} catch (err) {
console.error('加载订单失败:', err);
const tbody = document.getElementById('order-list');
if (tbody) {
tbody.innerHTML = '<tr><td colspan="7" class="text-center" style="color: red;">加载失败,请刷新重试</td></tr>';
}
API.toast('加载订单失败', 'error');
}
}
let materialRowIndex = 0;
function addMaterialRow(material = '', quantity = '', unitPrice = '') {
const container = document.getElementById('materials-container');
const index = materialRowIndex++;
const row = document.createElement('div');
row.className = 'material-row';
row.id = `material-row-${index}`;
row.style.cssText = 'display: grid; grid-template-columns: 2fr 1fr 1fr auto; gap: 12px; margin-bottom: 12px; padding: 16px; background: var(--surface-2); border-radius: 8px; border: 1px solid var(--border);';
row.innerHTML = `
<div class="field" style="margin: 0;">
<label>物料名称 <span style="color: var(--danger);">*</span></label>
<input type="text" class="input material-name" placeholder="如ETAP05 基站-5.0" value="${material}" required />
</div>
<div class="field" style="margin: 0;">
<label>数量 <span style="color: var(--danger);">*</span></label>
<input type="number" class="input material-quantity" placeholder="数量" value="${quantity}" min="1" required />
</div>
<div class="field" style="margin: 0;">
<label>单价 <span style="color: var(--danger);">*</span></label>
<input type="number" class="input material-price" placeholder="单价" value="${unitPrice}" step="0.01" min="0" required />
</div>
<div style="display: flex; align-items: flex-end; padding-bottom: 2px;">
<button type="button" class="btn btn-danger" onclick="window.CustomerOrder.removeMaterialRow(${index})" style="white-space: nowrap;">
🗑️ 删除
</button>
</div>
`;
container.appendChild(row);
}
function removeMaterialRow(index) {
const row = document.getElementById(`material-row-${index}`);
if (row) {
row.remove();
}
// 如果没有物料行了,至少保留一行
const container = document.getElementById('materials-container');
if (container.children.length === 0) {
addMaterialRow();
}
}
let currentEditId = null;
function openModal(order = null) {
const modal = document.getElementById('order-modal');
const title = document.getElementById('modal-title');
// 清空物料列表
const container = document.getElementById('materials-container');
container.innerHTML = '';
materialRowIndex = 0;
if (order) {
// 编辑模式
title.textContent = '编辑订单';
currentEditId = order.id;
document.getElementById('order-date').value = order.order_date || '';
document.getElementById('order-no').value = order.order_no || '';
document.getElementById('customer-name').value = order.customer_name || '';
// 添加物料信息
addMaterialRow(order.material || '', order.quantity || '', order.unit_price || '');
} else {
// 新增模式
title.textContent = '新增订单';
currentEditId = null;
document.getElementById('order-form').reset();
// 设置默认日期为今天
const today = new Date().toISOString().split('T')[0];
document.getElementById('order-date').value = today;
// 添加一行空物料
addMaterialRow();
}
modal.style.display = 'flex';
}
function closeModal() {
const modal = document.getElementById('order-modal');
modal.style.display = 'none';
document.getElementById('order-form').reset();
// 清空物料列表
const container = document.getElementById('materials-container');
if (container) {
container.innerHTML = '';
}
materialRowIndex = 0;
}
async function saveOrder() {
const orderDate = document.getElementById('order-date').value.trim();
const orderNo = document.getElementById('order-no').value.trim();
const customerName = document.getElementById('customer-name').value.trim();
if (!orderDate || !orderNo || !customerName) {
API.toast('请填写订单基本信息', 'error');
return;
}
// 收集所有物料信息
const materials = [];
const materialRows = document.querySelectorAll('.material-row');
if (materialRows.length === 0) {
API.toast('请至少添加一个物料', 'error');
return;
}
for (const row of materialRows) {
const materialName = row.querySelector('.material-name').value.trim();
const quantity = parseInt(row.querySelector('.material-quantity').value);
const unitPrice = parseFloat(row.querySelector('.material-price').value);
if (!materialName || !quantity || isNaN(unitPrice)) {
API.toast('请填写所有物料信息', 'error');
return;
}
if (quantity <= 0) {
API.toast('物料数量必须大于0', 'error');
return;
}
if (unitPrice < 0) {
API.toast('单价不能为负数', 'error');
return;
}
materials.push({
material: materialName,
quantity: quantity,
unit_price: unitPrice
});
}
try {
if (currentEditId) {
// 编辑模式:只能编辑单条记录
if (materials.length !== 1) {
API.toast('编辑模式下只能有一个物料', 'error');
return;
}
const mat = materials[0];
const res = await fetch(`/api/customer-orders/${currentEditId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
order_date: orderDate,
order_no: orderNo,
customer_name: customerName,
material: mat.material,
quantity: mat.quantity,
unit_price: mat.unit_price
})
});
const data = await res.json();
if (res.ok && data.ok) {
API.toast('更新成功', 'success');
closeModal();
await loadOrders();
} else {
API.toast(data.error || '更新失败', 'error');
}
} else {
// 新增模式:为每个物料创建一条订单记录
let successCount = 0;
for (const mat of materials) {
const res = await fetch('/api/customer-orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
order_date: orderDate,
order_no: orderNo,
customer_name: customerName,
material: mat.material,
quantity: mat.quantity,
unit_price: mat.unit_price
})
});
const data = await res.json();
if (res.ok && data.ok) {
successCount++;
} else {
console.error('保存物料失败:', mat.material, data.error);
}
}
if (successCount === materials.length) {
API.toast(`订单保存成功,共 ${successCount} 个物料`, 'success');
closeModal();
await loadOrders();
} else if (successCount > 0) {
API.toast(`部分保存成功(${successCount}/${materials.length}`, 'warning');
closeModal();
await loadOrders();
} else {
API.toast('保存失败', 'error');
}
}
} catch (err) {
console.error('保存订单失败:', err);
API.toast('保存失败', 'error');
}
}
async function deleteOrder(id) {
if (!confirm('确定要删除这条订单吗?')) {
return;
}
try {
const res = await fetch(`/api/customer-orders/${id}`, {
method: 'DELETE'
});
const data = await res.json();
if (res.ok && data.ok) {
API.toast('删除成功', 'success');
await loadOrders();
} else {
API.toast(data.error || '删除失败', 'error');
}
} catch (err) {
console.error('删除订单失败:', err);
API.toast('删除失败', 'error');
}
}
async function editOrder(id) {
try {
const res = await fetch('/api/customer-orders');
const data = await res.json();
const order = data.list.find(o => o.id === id);
if (!order) {
API.toast('订单不存在', 'error');
return;
}
openModal(order);
} catch (err) {
console.error('加载订单失败:', err);
API.toast('加载失败', 'error');
}
}
// 暴露给全局
window.CustomerOrder = {
openModal,
closeModal,
saveOrder,
editOrder,
deleteOrder,
addMaterialRow,
removeMaterialRow
};
})();