add
This commit is contained in:
@@ -489,6 +489,95 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配件编辑模态框 -->
|
||||
<div class="modal fade" id="partModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="partModalTitle">编辑配件</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="partForm">
|
||||
<input type="hidden" id="partId">
|
||||
<div class="row">
|
||||
<div class="col-6 mb-3">
|
||||
<label class="form-label">配件编号</label>
|
||||
<input type="text" class="form-control" id="partPartNo" required>
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<label class="form-label">配件名称</label>
|
||||
<input type="text" class="form-control" id="partPartName" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 mb-3">
|
||||
<label class="form-label">类别</label>
|
||||
<input type="text" class="form-control" id="partCategory" placeholder="如:机油滤清器">
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<label class="form-label">品牌</label>
|
||||
<input type="text" class="form-control" id="partBrand">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 mb-3">
|
||||
<label class="form-label">型号</label>
|
||||
<input type="text" class="form-control" id="partModel">
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<label class="form-label">单位</label>
|
||||
<select class="form-select" id="partUnit">
|
||||
<option value="个">个</option>
|
||||
<option value="套">套</option>
|
||||
<option value="桶">桶</option>
|
||||
<option value="瓶">瓶</option>
|
||||
<option value="对">对</option>
|
||||
<option value="件">件</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 mb-3">
|
||||
<label class="form-label">单价(元)</label>
|
||||
<input type="number" step="0.01" class="form-control" id="partUnitPrice" required>
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<label class="form-label">库存数量</label>
|
||||
<input type="number" class="form-control" id="partStockQuantity" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 mb-3">
|
||||
<label class="form-label">
|
||||
最小库存
|
||||
<span class="text-danger" title="低于此值将触发库存预警">*</span>
|
||||
</label>
|
||||
<input type="number" class="form-control" id="partMinStock" required>
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<label class="form-label">供应商</label>
|
||||
<input type="text" class="form-control" id="partSupplier">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">仓库位置</label>
|
||||
<input type="text" class="form-control" id="partWarehouseLocation" placeholder="如:A区01架02层">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">备注</label>
|
||||
<textarea class="form-control" id="partRemark" rows="2"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" onclick="savePart()">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast 通知 -->
|
||||
<div class="toast-container position-fixed top-0 end-0 p-3">
|
||||
<div id="liveToast" class="toast" role="alert">
|
||||
|
||||
@@ -484,19 +484,42 @@ function filterOrders(status) {
|
||||
|
||||
// 查看配件详情
|
||||
async function viewPart(id) {
|
||||
Utils.loading(true);
|
||||
try {
|
||||
const response = await http.get(API.PART(id));
|
||||
if (response.code === 200 && response.data) {
|
||||
const part = response.data;
|
||||
// 显示配件详情
|
||||
Utils.showToast('配件名称: ' + part.partName + '\n库存: ' + part.stockQuantity + part.unit, 'info');
|
||||
}
|
||||
} catch (error) {
|
||||
Utils.showToast('加载配件信息失败', 'error');
|
||||
} finally {
|
||||
Utils.loading(false);
|
||||
const part = allPartsData.find(p => p.partId === id);
|
||||
if (!part) {
|
||||
Utils.showToast('配件不存在', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 填充表单
|
||||
document.getElementById('partId').value = part.partId;
|
||||
document.getElementById('partPartNo').value = part.partNo;
|
||||
document.getElementById('partPartName').value = part.partName;
|
||||
document.getElementById('partCategory').value = part.category || '';
|
||||
document.getElementById('partBrand').value = part.brand || '';
|
||||
document.getElementById('partModel').value = part.model || '';
|
||||
document.getElementById('partUnit').value = part.unit || '个';
|
||||
document.getElementById('partUnitPrice').value = part.unitPrice;
|
||||
document.getElementById('partStockQuantity').value = part.stockQuantity;
|
||||
document.getElementById('partMinStock').value = part.minStock;
|
||||
document.getElementById('partSupplier').value = part.supplier || '';
|
||||
document.getElementById('partWarehouseLocation').value = part.warehouseLocation || '';
|
||||
document.getElementById('partRemark').value = part.remark || '';
|
||||
|
||||
// 禁用表单(只读模式)
|
||||
document.getElementById('partForm').querySelectorAll('input, select, textarea').forEach(el => {
|
||||
el.disabled = true;
|
||||
});
|
||||
|
||||
document.getElementById('partModalTitle').textContent = '查看配件详情';
|
||||
const modal = new bootstrap.Modal(document.getElementById('partModal'));
|
||||
modal.show();
|
||||
|
||||
// 启用表单控件(在关闭时)
|
||||
document.getElementById('partModal').addEventListener('hidden.bs.modal', function() {
|
||||
document.getElementById('partForm').querySelectorAll('input, select, textarea').forEach(el => {
|
||||
el.disabled = false;
|
||||
});
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
// 编辑配件
|
||||
@@ -507,33 +530,81 @@ async function editPart(id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 简单实现:使用prompt编辑
|
||||
const newStock = prompt('请输入新的库存数量:', part.stockQuantity);
|
||||
if (newStock !== null && !isNaN(newStock)) {
|
||||
Utils.loading(true);
|
||||
try {
|
||||
const response = await http.put(API.PART(id), {
|
||||
...part,
|
||||
stockQuantity: parseInt(newStock)
|
||||
});
|
||||
// 填充表单
|
||||
document.getElementById('partId').value = part.partId;
|
||||
document.getElementById('partPartNo').value = part.partNo;
|
||||
document.getElementById('partPartName').value = part.partName;
|
||||
document.getElementById('partCategory').value = part.category || '';
|
||||
document.getElementById('partBrand').value = part.brand || '';
|
||||
document.getElementById('partModel').value = part.model || '';
|
||||
document.getElementById('partUnit').value = part.unit || '个';
|
||||
document.getElementById('partUnitPrice').value = part.unitPrice;
|
||||
document.getElementById('partStockQuantity').value = part.stockQuantity;
|
||||
document.getElementById('partMinStock').value = part.minStock;
|
||||
document.getElementById('partSupplier').value = part.supplier || '';
|
||||
document.getElementById('partWarehouseLocation').value = part.warehouseLocation || '';
|
||||
document.getElementById('partRemark').value = part.remark || '';
|
||||
|
||||
if (response.code === 200) {
|
||||
Utils.showToast('更新成功', 'success');
|
||||
loadAllParts();
|
||||
} else {
|
||||
Utils.showToast(response.message || '更新失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
Utils.showToast('更新失败', 'error');
|
||||
} finally {
|
||||
Utils.loading(false);
|
||||
document.getElementById('partModalTitle').textContent = '编辑配件';
|
||||
const modal = new bootstrap.Modal(document.getElementById('partModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// 保存配件
|
||||
async function savePart() {
|
||||
const partId = document.getElementById('partId').value;
|
||||
const partData = {
|
||||
partNo: document.getElementById('partPartNo').value,
|
||||
partName: document.getElementById('partPartName').value,
|
||||
category: document.getElementById('partCategory').value,
|
||||
brand: document.getElementById('partBrand').value,
|
||||
model: document.getElementById('partModel').value,
|
||||
unit: document.getElementById('partUnit').value,
|
||||
unitPrice: parseFloat(document.getElementById('partUnitPrice').value),
|
||||
stockQuantity: parseInt(document.getElementById('partStockQuantity').value),
|
||||
minStock: parseInt(document.getElementById('partMinStock').value),
|
||||
supplier: document.getElementById('partSupplier').value,
|
||||
warehouseLocation: document.getElementById('partWarehouseLocation').value,
|
||||
remark: document.getElementById('partRemark').value
|
||||
};
|
||||
|
||||
Utils.loading(true);
|
||||
try {
|
||||
let response;
|
||||
if (partId) {
|
||||
// 更新
|
||||
response = await http.put(API.PART(partId), partData);
|
||||
} else {
|
||||
// 新增
|
||||
response = await http.post(API.PARTS, partData);
|
||||
}
|
||||
|
||||
if (response.code === 200) {
|
||||
Utils.showToast(partId ? '更新成功' : '添加成功', 'success');
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('partModal'));
|
||||
modal.hide();
|
||||
loadAllParts();
|
||||
} else {
|
||||
Utils.showToast(response.message || '保存失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存配件失败:', error);
|
||||
Utils.showToast('保存失败', 'error');
|
||||
} finally {
|
||||
Utils.loading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加配件
|
||||
function showAddPartModal() {
|
||||
Utils.showToast('添加配件功能开发中...', 'info');
|
||||
// 清空表单
|
||||
document.getElementById('partForm').reset();
|
||||
document.getElementById('partId').value = '';
|
||||
document.getElementById('partUnit').value = '个';
|
||||
|
||||
document.getElementById('partModalTitle').textContent = '添加配件';
|
||||
const modal = new bootstrap.Modal(document.getElementById('partModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// ==================== 数据加载函数 ====================
|
||||
@@ -578,6 +649,9 @@ async function loadAllData() {
|
||||
// 加载预约数据
|
||||
await loadAppointments();
|
||||
|
||||
// 加载管理员首页统计数据和最近工单
|
||||
await loadAdminStatsData();
|
||||
|
||||
console.log('所有数据加载完成');
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
@@ -587,6 +661,83 @@ async function loadAllData() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载管理员首页数据(统计和最近工单)
|
||||
async function loadAdminStatsData() {
|
||||
try {
|
||||
// 加载统计数据
|
||||
const [usersRes, vehiclesRes, ordersRes, partsRes] = await Promise.all([
|
||||
http.get(API.USERS),
|
||||
http.get(API.VEHICLES),
|
||||
http.get(API.ORDERS),
|
||||
http.get(API.PARTS_LOW_STOCK)
|
||||
]);
|
||||
|
||||
if (usersRes.code === 200) {
|
||||
updateStat('totalUsers', usersRes.data?.length || 0);
|
||||
}
|
||||
|
||||
if (vehiclesRes.code === 200) {
|
||||
updateStat('totalVehicles', vehiclesRes.data?.length || 0);
|
||||
}
|
||||
|
||||
if (ordersRes.code === 200) {
|
||||
updateStat('totalOrders', ordersRes.data?.length || 0);
|
||||
// 显示最近工单(前5条)
|
||||
const recentOrders = ordersRes.data.slice(0, 5);
|
||||
await displayRecentOrders(recentOrders);
|
||||
}
|
||||
|
||||
if (partsRes.code === 200) {
|
||||
updateStat('lowStockParts', partsRes.data?.length || 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示最近工单
|
||||
async function displayRecentOrders(orders) {
|
||||
const tbody = document.getElementById('recentOrdersTableBody');
|
||||
if (!tbody) return;
|
||||
|
||||
if (orders.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted py-4">暂无数据</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有车辆信息
|
||||
const vehiclesRes = await http.get(API.VEHICLES);
|
||||
const vehicles = vehiclesRes.data || [];
|
||||
|
||||
tbody.innerHTML = orders.map(o => {
|
||||
const vehicle = vehicles.find(v => v.vehicleId === o.vehicleId);
|
||||
return `
|
||||
<tr>
|
||||
<td>${o.orderNo}</td>
|
||||
<td>${Utils.getServiceTypeText(o.serviceType)}</td>
|
||||
<td>${vehicle ? vehicle.licensePlate : '-'}</td>
|
||||
<td>${Utils.formatMoney(o.totalCost)}</td>
|
||||
<td>${Utils.getStatusBadge(o.status)}</td>
|
||||
<td>${Utils.formatDateTime(o.createTime)}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 更新统计数字
|
||||
function updateStat(id, value) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
el.textContent = value;
|
||||
// 添加动画效果
|
||||
el.style.transition = 'all 0.3s';
|
||||
el.style.transform = 'scale(1.2)';
|
||||
setTimeout(() => {
|
||||
el.style.transform = 'scale(1)';
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载所有配件
|
||||
async function loadAllParts() {
|
||||
Utils.loading(true);
|
||||
|
||||
@@ -275,17 +275,21 @@ async function loadCustomerData() {
|
||||
if (!user) return;
|
||||
|
||||
try {
|
||||
// 获取所有用户,找到自己的customer_id
|
||||
const usersRes = await http.get(API.USERS);
|
||||
if (usersRes.code === 200 && usersRes.data) {
|
||||
const customers = usersRes.data.filter(u => u.role === 'customer');
|
||||
const currentUser = customers.find(c => c.userId === user.userId);
|
||||
// 获取用户信息、客户信息、车辆信息
|
||||
const [usersRes, customersRes] = await Promise.all([
|
||||
http.get(API.USERS),
|
||||
http.get(API.CUSTOMERS)
|
||||
]);
|
||||
|
||||
if (currentUser) {
|
||||
// 加载车辆、工单、预约数据
|
||||
loadCustomerVehicles(currentUser.userId);
|
||||
loadCustomerOrders(currentUser.userId);
|
||||
loadCustomerAppointments(currentUser.userId);
|
||||
if (usersRes.code === 200 && customersRes.code === 200) {
|
||||
// 找到当前用户对应的客户记录
|
||||
const currentCustomer = customersRes.data.find(c => c.userId === user.userId);
|
||||
|
||||
if (currentCustomer) {
|
||||
// 加载车辆、工单、预约数据(使用customer_id而不是user_id)
|
||||
loadCustomerVehicles(currentCustomer.customerId);
|
||||
loadCustomerOrders(currentCustomer.customerId);
|
||||
loadCustomerAppointments(currentCustomer.customerId);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -294,13 +298,13 @@ async function loadCustomerData() {
|
||||
}
|
||||
|
||||
// 加载客户车辆
|
||||
async function loadCustomerVehicles(userId) {
|
||||
async function loadCustomerVehicles(customerId) {
|
||||
try {
|
||||
const response = await http.get(API.VEHICLES);
|
||||
// 使用专门的API端点获取客户的车辆,而不是获取所有车辆再过滤
|
||||
const response = await http.get(API.VEHICLE_CUSTOMER(customerId));
|
||||
if (response.code === 200 && response.data) {
|
||||
const myVehicles = response.data.filter(v => v.customerId === userId);
|
||||
displayVehicles(myVehicles);
|
||||
populateVehicleSelect(myVehicles);
|
||||
displayVehicles(response.data);
|
||||
populateVehicleSelect(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载车辆失败:', error);
|
||||
@@ -352,10 +356,10 @@ function populateVehicleSelect(vehicles) {
|
||||
}
|
||||
|
||||
// 加载客户工单
|
||||
async function loadCustomerOrders(userId) {
|
||||
async function loadCustomerOrders(customerId) {
|
||||
try {
|
||||
const [ordersRes, vehiclesRes] = await Promise.all([
|
||||
http.get(API.ORDER_CUSTOMER(userId)),
|
||||
http.get(API.ORDER_CUSTOMER(customerId)),
|
||||
http.get(API.VEHICLES)
|
||||
]);
|
||||
|
||||
@@ -394,10 +398,10 @@ function displayOrders(orders, vehicles) {
|
||||
}
|
||||
|
||||
// 加载客户预约
|
||||
async function loadCustomerAppointments(userId) {
|
||||
async function loadCustomerAppointments(customerId) {
|
||||
try {
|
||||
const [appointmentsRes, vehiclesRes] = await Promise.all([
|
||||
http.get(API.APPOINTMENT_CUSTOMER(userId)),
|
||||
http.get(API.APPOINTMENT_CUSTOMER(customerId)),
|
||||
http.get(API.VEHICLES)
|
||||
]);
|
||||
|
||||
@@ -445,10 +449,16 @@ async function cancelAppointment(id) {
|
||||
const response = await http.put(API.APPOINTMENT_CANCEL(id), {});
|
||||
if (response.code === 200) {
|
||||
Utils.showToast('预约已取消', 'success');
|
||||
// 重新加载预约列表
|
||||
// 重新加载预约列表 - 需要获取customer_id
|
||||
const user = Utils.getCurrentUser();
|
||||
if (user) {
|
||||
loadCustomerAppointments(user.userId);
|
||||
const customersRes = await http.get(API.CUSTOMERS);
|
||||
if (customersRes.code === 200) {
|
||||
const currentCustomer = customersRes.data.find(c => c.userId === user.userId);
|
||||
if (currentCustomer) {
|
||||
loadCustomerAppointments(currentCustomer.customerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Utils.showToast(response.message || '取消失败', 'error');
|
||||
@@ -490,8 +500,23 @@ async function submitAppointment() {
|
||||
|
||||
Utils.loading(true);
|
||||
try {
|
||||
// 获取customer_id
|
||||
const customersRes = await http.get(API.CUSTOMERS);
|
||||
if (customersRes.code !== 200) {
|
||||
Utils.showToast('获取客户信息失败', 'error');
|
||||
Utils.loading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentCustomer = customersRes.data.find(c => c.userId === user.userId);
|
||||
if (!currentCustomer) {
|
||||
Utils.showToast('未找到客户信息', 'error');
|
||||
Utils.loading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await http.post(API.APPOINTMENTS, {
|
||||
customerId: user.userId,
|
||||
customerId: currentCustomer.customerId,
|
||||
vehicleId: parseInt(vehicleId),
|
||||
serviceType: serviceType,
|
||||
appointmentTime: appointmentTime,
|
||||
@@ -504,7 +529,7 @@ async function submitAppointment() {
|
||||
// 重置表单
|
||||
document.getElementById('appointmentForm').reset();
|
||||
// 重新加载预约列表
|
||||
loadCustomerAppointments(user.userId);
|
||||
loadCustomerAppointments(currentCustomer.customerId);
|
||||
} else {
|
||||
Utils.showToast(response.message || '预约失败', 'error');
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ const API = {
|
||||
USERS_ROLE: (role) => `/users/role/${role}`,
|
||||
CHANGE_PASSWORD: (id) => `/users/${id}/password`,
|
||||
|
||||
// 客户
|
||||
CUSTOMERS: '/customers',
|
||||
CUSTOMER: (id) => `/customers/${id}`,
|
||||
|
||||
// 车辆
|
||||
VEHICLES: '/vehicles',
|
||||
VEHICLE: (id) => `/vehicles/${id}`,
|
||||
|
||||
Reference in New Issue
Block a user