add
This commit is contained in:
@@ -4,267 +4,362 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>工作人员仪表板 - 车管家4S店车辆维保管理系统</title>
|
||||
<link rel="stylesheet" href="../css/common.css">
|
||||
<link rel="stylesheet" href="../css/dashboard.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="dashboard-container">
|
||||
<div class="sidebar">
|
||||
<div class="dashboard-wrapper">
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2>车管家系统</h2>
|
||||
<p>工作人员控制台</p>
|
||||
<i class="bi bi-car-front-fill fs-1 d-block mb-2"></i>
|
||||
<h5 class="mb-1">车管家系统</h5>
|
||||
<small>工作人员控制台</small>
|
||||
</div>
|
||||
<div class="sidebar-menu">
|
||||
<div class="menu-item active" onclick="showSection('overview')">
|
||||
<span class="menu-icon">📊</span>
|
||||
<div class="mt-4">
|
||||
<a class="menu-item active" onclick="showSection('overview')">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
<span>工作概览</span>
|
||||
</div>
|
||||
<div class="menu-item" onclick="showSection('myorders')">
|
||||
<span class="menu-icon">📋</span>
|
||||
</a>
|
||||
<a class="menu-item" onclick="showSection('myorders')">
|
||||
<i class="bi bi-clipboard-data"></i>
|
||||
<span>我的工单</span>
|
||||
</div>
|
||||
<div class="menu-item" onclick="showSection('vehicles')">
|
||||
<span class="menu-icon">🚗</span>
|
||||
</a>
|
||||
<a class="menu-item" onclick="showSection('search-vehicle')">
|
||||
<i class="bi bi-search"></i>
|
||||
<span>车辆查询</span>
|
||||
</div>
|
||||
<div class="menu-item" onclick="showSection('parts')">
|
||||
<span class="menu-icon">🔧</span>
|
||||
</a>
|
||||
<a class="menu-item" onclick="showSection('search-parts')">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<span>配件查询</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="top-nav">
|
||||
<div class="top-nav-left">
|
||||
<h1>工作人员仪表板</h1>
|
||||
<main class="main-content">
|
||||
<div class="top-navbar">
|
||||
<div class="d-flex align-items-center">
|
||||
<h5 class="mb-0 me-3">
|
||||
<i class="bi bi-speedometer2"></i> 工作人员仪表板
|
||||
</h5>
|
||||
</div>
|
||||
<div class="top-nav-right">
|
||||
<div class="user-info">
|
||||
<div class="user-avatar" id="userAvatar">S</div>
|
||||
<span class="user-name" id="userName">工作人员</span>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-light dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<span id="userName">工作人员</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><h6 class="dropdown-header">当前角色</h6></li>
|
||||
<li><span class="dropdown-item" id="userRole"></span></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="Utils.logout()">
|
||||
<i class="bi bi-box-arrow-right"></i> 退出登录
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<button class="btn-logout" onclick="utils.logout()">退出登录</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="overview-section" class="section">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon blue">📋</div>
|
||||
<div class="stat-info">
|
||||
<h3 id="myOrdersCount">0</h3>
|
||||
<p>我的工单</p>
|
||||
<!-- 工作概览 -->
|
||||
<div id="overview-section" class="content-section">
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon blue">
|
||||
<i class="bi bi-clipboard-data-fill"></i>
|
||||
</div>
|
||||
<div class="stat-value" id="myOrdersCount">0</div>
|
||||
<div class="stat-label">我的工单</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon orange">⏳</div>
|
||||
<div class="stat-info">
|
||||
<h3 id="inProgressCount">0</h3>
|
||||
<p>进行中</p>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon orange">
|
||||
<i class="bi bi-hourglass-split"></i>
|
||||
</div>
|
||||
<div class="stat-value" id="inProgressCount">0</div>
|
||||
<div class="stat-label">进行中</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon green">✅</div>
|
||||
<div class="stat-info">
|
||||
<h3 id="completedCount">0</h3>
|
||||
<p>已完成</p>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon green">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
</div>
|
||||
<div class="stat-value" id="completedCount">0</div>
|
||||
<div class="stat-label">已完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-card">
|
||||
<div class="content-header">
|
||||
<h2>待处理工单</h2>
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-clock-history"></i> 待处理工单</h6>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>工单编号</th>
|
||||
<th>服务类型</th>
|
||||
<th>车牌号</th>
|
||||
<th>状态</th>
|
||||
<th>预约时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pendingOrdersBody">
|
||||
<tr><td colspan="6" class="empty-state">暂无待处理工单</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>工单编号</th>
|
||||
<th>服务类型</th>
|
||||
<th>车牌号</th>
|
||||
<th>状态</th>
|
||||
<th>预约时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pendingOrdersTableBody">
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted py-4">
|
||||
<div class="spinner-border spinner-border-sm me-2"></div>
|
||||
加载中...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="myorders-section" class="section" style="display: none;">
|
||||
<!-- 我的工单 -->
|
||||
<div id="myorders-section" class="content-section" style="display: none;">
|
||||
<div class="content-card">
|
||||
<div class="content-header">
|
||||
<h2>我的工单列表</h2>
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-clipboard-data"></i> 我的工单列表</h6>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>工单编号</th>
|
||||
<th>服务类型</th>
|
||||
<th>车牌号</th>
|
||||
<th>客户姓名</th>
|
||||
<th>状态</th>
|
||||
<th>创建时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="myOrdersBody">
|
||||
<tr><td colspan="7" class="empty-state">加载中...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>工单编号</th>
|
||||
<th>服务类型</th>
|
||||
<th>车牌号</th>
|
||||
<th>客户姓名</th>
|
||||
<th>状态</th>
|
||||
<th>创建时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="myOrdersTableBody">
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted py-4">加载中...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="vehicles-section" class="section" style="display: none;">
|
||||
<div class="toolbar">
|
||||
<div class="search-box">
|
||||
<input type="text" id="searchVehicle" placeholder="输入车牌号查询...">
|
||||
<button onclick="searchVehicle()">🔍</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 车辆查询 -->
|
||||
<div id="search-vehicle-section" class="content-section" style="display: none;">
|
||||
<div class="content-card">
|
||||
<div class="content-body">
|
||||
<div id="vehicleResult"></div>
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-search"></i> 车辆信息查询</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-4">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-car-front"></i></span>
|
||||
<input type="text" class="form-control" id="searchVehiclePlate" placeholder="请输入车牌号查询...">
|
||||
<button class="btn btn-primary" onclick="searchVehicle()">
|
||||
<i class="bi bi-search"></i> 查询
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="vehicleSearchResult">
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-car-front"></i>
|
||||
<p>请输入车牌号进行查询</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="parts-section" class="section" style="display: none;">
|
||||
<div class="toolbar">
|
||||
<div class="search-box">
|
||||
<input type="text" id="searchPart" placeholder="搜索配件...">
|
||||
<button onclick="searchParts()">🔍</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配件查询 -->
|
||||
<div id="search-parts-section" class="content-section" style="display: none;">
|
||||
<div class="content-card">
|
||||
<div class="content-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>配件编号</th>
|
||||
<th>配件名称</th>
|
||||
<th>类别</th>
|
||||
<th>库存数量</th>
|
||||
<th>单价</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="partsBody">
|
||||
<tr><td colspan="5" class="empty-state">请输入关键词搜索配件</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-box-seam"></i> 配件库存查询</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-4">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-search"></i></span>
|
||||
<input type="text" class="form-control" id="searchPartKeyword" placeholder="请输入配件名称或编号...">
|
||||
<button class="btn btn-primary" onclick="searchParts()">
|
||||
<i class="bi bi-search"></i> 搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="partsSearchResult">
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<p>请输入关键词搜索配件</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Toast 通知 -->
|
||||
<div class="toast-container position-fixed top-0 end-0 p-3">
|
||||
<div id="liveToast" class="toast" role="alert">
|
||||
<div class="toast-header">
|
||||
<i class="bi bi-bell me-2" id="toastIcon"></i>
|
||||
<strong class="me-auto" id="toastTitle">提示</strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
|
||||
</div>
|
||||
<div class="toast-body" id="toastMessage"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading 遮罩 -->
|
||||
<div id="loadingOverlay" class="loading-overlay d-none">
|
||||
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="../js/config.js"></script>
|
||||
<script src="../js/api.js"></script>
|
||||
<script>
|
||||
if (!utils.checkAuth() || !utils.hasRole('staff')) {
|
||||
window.location.href = '../login.html';
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const user = utils.getCurrentUser();
|
||||
if (user) {
|
||||
document.getElementById('userName').textContent = user.realName;
|
||||
document.getElementById('userAvatar').textContent = user.realName.charAt(0);
|
||||
loadStaffData(user.userId);
|
||||
}
|
||||
});
|
||||
|
||||
function showSection(name) {
|
||||
document.querySelectorAll('.section').forEach(s => s.style.display = 'none');
|
||||
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
|
||||
event.currentTarget.classList.add('active');
|
||||
document.getElementById(name + '-section').style.display = 'block';
|
||||
}
|
||||
|
||||
async function loadStaffData(staffId) {
|
||||
try {
|
||||
const response = await api.get(API_ENDPOINTS.ORDERS);
|
||||
if (response.code === 200 && response.data) {
|
||||
const myOrders = response.data.filter(o => o.staffId === staffId);
|
||||
const inProgress = myOrders.filter(o => o.status === 'in_progress');
|
||||
const completed = myOrders.filter(o => o.status === 'completed');
|
||||
|
||||
document.getElementById('myOrdersCount').textContent = myOrders.length;
|
||||
document.getElementById('inProgressCount').textContent = inProgress.length;
|
||||
document.getElementById('completedCount').textContent = completed.length;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 车辆查询
|
||||
async function searchVehicle() {
|
||||
const plate = document.getElementById('searchVehicle').value.trim();
|
||||
const plate = document.getElementById('searchVehiclePlate').value.trim();
|
||||
if (!plate) {
|
||||
utils.showError('请输入车牌号');
|
||||
Utils.showToast('请输入车牌号', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
Utils.loading(true);
|
||||
try {
|
||||
const response = await api.get(API_ENDPOINTS.VEHICLE_BY_PLATE(plate));
|
||||
const response = await http.get(API.VEHICLE_PLATE(plate));
|
||||
const resultDiv = document.getElementById('vehicleSearchResult');
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
const v = response.data;
|
||||
document.getElementById('vehicleResult').innerHTML = `
|
||||
<div class="card">
|
||||
<h3>车辆信息</h3>
|
||||
<p><strong>车牌号:</strong> ${v.licensePlate}</p>
|
||||
<p><strong>品牌型号:</strong> ${v.brand} ${v.model}</p>
|
||||
<p><strong>颜色:</strong> ${v.color || '-'}</p>
|
||||
<p><strong>车架号:</strong> ${v.vin || '-'}</p>
|
||||
<p><strong>里程数:</strong> ${v.mileage || 0} 公里</p>
|
||||
<p><strong>状态:</strong> ${utils.getStatusBadge(v.status)}</p>
|
||||
resultDiv.innerHTML = `
|
||||
<div class="card border-primary mb-3">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<i class="bi bi-car-front"></i> 车辆信息
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-2"><strong>车牌号:</strong> <span class="badge bg-primary fs-6">${v.licensePlate}</span></p>
|
||||
<p class="mb-2"><strong>品牌型号:</strong> ${v.brand} ${v.model}</p>
|
||||
<p class="mb-2"><strong>颜色:</strong> ${v.color || '-'}</p>
|
||||
<p class="mb-2"><strong>车架号:</strong> ${v.vin || '-'}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="mb-2"><strong>当前里程:</strong> ${v.mileage || 0} 公里</p>
|
||||
<p class="mb-2"><strong>上次保养:</strong> ${Utils.formatDate(v.lastMaintenanceDate)}</p>
|
||||
<p class="mb-2"><strong>下次保养:</strong> ${Utils.formatDate(v.nextMaintenanceDate)}</p>
|
||||
<p class="mb-0"><strong>车辆状态:</strong> ${Utils.getStatusBadge(v.status)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
document.getElementById('vehicleResult').innerHTML = '<p class="empty-state">未找到该车辆</p>';
|
||||
resultDiv.innerHTML = `
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-triangle"></i> 未找到该车辆
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
utils.showError('查询失败');
|
||||
console.error('查询失败:', error);
|
||||
Utils.showToast('查询失败', 'error');
|
||||
} finally {
|
||||
Utils.loading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 配件搜索
|
||||
async function searchParts() {
|
||||
const keyword = document.getElementById('searchPartKeyword').value.trim();
|
||||
if (!keyword) {
|
||||
Utils.showToast('请输入搜索关键词', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
Utils.loading(true);
|
||||
try {
|
||||
const response = await api.get(API_ENDPOINTS.PARTS);
|
||||
const response = await http.get(API.PARTS);
|
||||
const resultDiv = document.getElementById('partsSearchResult');
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
const tbody = document.getElementById('partsBody');
|
||||
const keyword = document.getElementById('searchPart').value.toLowerCase();
|
||||
const filtered = response.data.filter(p =>
|
||||
p.partName.toLowerCase().includes(keyword) ||
|
||||
p.partNo.toLowerCase().includes(keyword)
|
||||
p.partName.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
p.partNo.toLowerCase().includes(keyword.toLowerCase())
|
||||
);
|
||||
|
||||
if (filtered.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">未找到配件</td></tr>';
|
||||
resultDiv.innerHTML = `
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> 未找到匹配的配件
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = filtered.map(p => `
|
||||
<tr>
|
||||
<td>${p.partNo}</td>
|
||||
<td>${p.partName}</td>
|
||||
<td>${p.category || '-'}</td>
|
||||
<td>${p.stockQuantity} ${p.unit}</td>
|
||||
<td>¥${p.unitPrice}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
resultDiv.innerHTML = `
|
||||
<div class="table-responsive">
|
||||
<table class="table table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>配件编号</th>
|
||||
<th>配件名称</th>
|
||||
<th>类别</th>
|
||||
<th>库存数量</th>
|
||||
<th>单价</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${filtered.map(p => `
|
||||
<tr>
|
||||
<td>${p.partNo}</td>
|
||||
<td>${p.partName}</td>
|
||||
<td>${p.category || '-'}</td>
|
||||
<td>
|
||||
${p.stockQuantity} ${p.unit}
|
||||
${p.stockQuantity <= p.minStock ? '<span class="badge bg-danger ms-2">库存预警</span>' : ''}
|
||||
</td>
|
||||
<td>${Utils.formatMoney(p.unitPrice)}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
utils.showError('搜索失败');
|
||||
console.error('搜索失败:', error);
|
||||
Utils.showToast('搜索失败', 'error');
|
||||
} finally {
|
||||
Utils.loading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 回车搜索
|
||||
document.getElementById('searchVehiclePlate')?.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') searchVehicle();
|
||||
});
|
||||
|
||||
document.getElementById('searchPartKeyword')?.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') searchParts();
|
||||
});
|
||||
</script>
|
||||
<script src="../js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user