This commit is contained in:
wangziqi
2026-01-07 15:39:54 +08:00
parent 81e6c3eeb8
commit 7c4be0b7b4
20 changed files with 3792 additions and 832 deletions

View File

@@ -4,358 +4,244 @@
<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('myvehicles')">
<span class="menu-icon">🚗</span>
<div class="mt-4">
<a class="menu-item active" onclick="showSection('myvehicles')">
<i class="bi bi-car-front"></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('appointments')">
<span class="menu-icon">📅</span>
</a>
<a class="menu-item" onclick="showSection('appointments')">
<i class="bi bi-calendar-check"></i>
<span>我的预约</span>
</div>
<div class="menu-item" onclick="showSection('newappointment')">
<span class="menu-icon"></span>
</a>
<a class="menu-item" onclick="showSection('newappointment')">
<i class="bi bi-plus-circle"></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-person-circle"></i> 客户中心
</h5>
</div>
<div class="top-nav-right">
<div class="user-info">
<div class="user-avatar" id="userAvatar">C</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="myvehicles-section" class="section">
<!-- 我的车辆 -->
<div id="myvehicles-section" class="content-section">
<div class="content-card">
<div class="content-header">
<h2>我的车辆</h2>
<div class="card-header">
<h6 class="mb-0"><i class="bi bi-car-front"></i> 我的车辆</h6>
</div>
<div class="content-body">
<div id="vehiclesGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:20px;">
<p class="empty-state">加载中...</p>
<div class="card-body">
<div class="row" id="vehiclesContainer">
<div class="col-12 text-center text-muted py-5">
<div class="spinner-border text-primary mb-3" role="status"></div>
<p>加载中...</p>
</div>
</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>
</tr>
</thead>
<tbody id="ordersBody">
<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="ordersTableBody">
<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="appointments-section" class="section" style="display: none;">
<!-- 我的预约 -->
<div id="appointments-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-calendar-check"></i> 我的预约</h6>
</div>
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>服务类型</th>
<th>车牌号</th>
<th>预约时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="appointmentsBody">
<tr><td colspan="5" 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>
</tr>
</thead>
<tbody id="appointmentsTableBody">
<tr>
<td colspan="5" 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="newappointment-section" class="section" style="display: none;">
<!-- 在线预约 -->
<div id="newappointment-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-plus-circle"></i> 在线预约服务</h6>
</div>
<div class="content-body">
<form id="appointmentForm" style="max-width:600px;">
<div class="form-group">
<label>选择车辆</label>
<select id="vehicleSelect" required>
<div class="card-body">
<form id="appointmentForm" style="max-width: 600px;">
<div class="mb-3">
<label class="form-label">
<i class="bi bi-car-front"></i> 选择车辆
<span class="text-danger">*</span>
</label>
<select class="form-select" id="vehicleSelect" required>
<option value="">请选择车辆</option>
</select>
</div>
<div class="form-group">
<label>服务类型</label>
<select id="serviceType" required>
<div class="mb-3">
<label class="form-label">
<i class="bi bi-gear"></i> 服务类型
<span class="text-danger">*</span>
</label>
<select class="form-select" id="serviceType" required>
<option value="maintenance">保养维护</option>
<option value="repair">维修服务</option>
<option value="beauty">美容服务</option>
<option value="insurance">保险代理</option>
</select>
</div>
<div class="form-group">
<label>预约时间</label>
<input type="datetime-local" id="appointmentTime" required>
<div class="mb-3">
<label class="form-label">
<i class="bi bi-calendar"></i> 预约时间
<span class="text-danger">*</span>
</label>
<input type="datetime-local" class="form-control" id="appointmentTime" required>
</div>
<div class="form-group">
<label>联系电话</label>
<input type="tel" id="contactPhone" required>
<div class="mb-3">
<label class="form-label">
<i class="bi bi-telephone"></i> 联系电话
<span class="text-danger">*</span>
</label>
<input type="tel" class="form-control" id="contactPhone" placeholder="请输入联系电话" required>
</div>
<div class="form-group">
<label>预约说明</label>
<textarea id="description" placeholder="请描述您的需求"></textarea>
<div class="mb-4">
<label class="form-label">
<i class="bi bi-chat-text"></i> 预约说明
</label>
<textarea class="form-control" id="description" rows="4" placeholder="请描述您的服务需求..."></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle"></i> 提交预约
</button>
<button type="reset" class="btn btn-secondary">
<i class="bi bi-x-circle"></i> 重置
</button>
</div>
<button type="submit" class="btn btn-primary">提交预约</button>
</form>
</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 src="../js/app.js"></script>
<script>
if (!utils.checkAuth() || !utils.hasRole('customer')) {
window.location.href = '../login.html';
}
let currentCustomerId = null;
window.addEventListener('DOMContentLoaded', async () => {
const user = utils.getCurrentUser();
if (user) {
document.getElementById('userName').textContent = user.realName;
document.getElementById('userAvatar').textContent = user.realName.charAt(0);
document.getElementById('contactPhone').value = user.phone;
await loadCustomerData(user.userId);
}
});
async function loadCustomerData(userId) {
try {
const usersRes = await api.get(API_ENDPOINTS.USERS);
const customers = usersRes.data?.filter(u => u.role === 'customer') || [];
const currentUser = customers.find(c => c.userId === userId);
if (currentUser) {
currentCustomerId = currentUser.userId;
loadVehicles();
loadOrders();
loadAppointments();
loadVehicleOptions();
}
} catch (error) {
console.error('加载数据失败:', error);
}
}
async function loadVehicles() {
try {
const response = await api.get(API_ENDPOINTS.VEHICLES);
if (response.code === 200 && response.data) {
const myVehicles = response.data.filter(v => v.customerId === currentCustomerId);
displayVehicles(myVehicles);
}
} catch (error) {
console.error('加载车辆失败:', error);
}
}
function displayVehicles(vehicles) {
const grid = document.getElementById('vehiclesGrid');
if (vehicles.length === 0) {
grid.innerHTML = '<p class="empty-state">暂无车辆</p>';
return;
}
grid.innerHTML = vehicles.map(v => `
<div class="card">
<h3 style="color:#1890ff;">${v.licensePlate}</h3>
<p><strong>品牌:</strong> ${v.brand} ${v.model}</p>
<p><strong>颜色:</strong> ${v.color || '-'}</p>
<p><strong>里程:</strong> ${v.mileage || 0} 公里</p>
<p><strong>上次保养:</strong> ${utils.formatDate(v.lastMaintenanceDate)}</p>
<p><strong>下次保养:</strong> ${utils.formatDate(v.nextMaintenanceDate)}</p>
<p><strong>状态:</strong> ${utils.getStatusBadge(v.status)}</p>
</div>
`).join('');
}
async function loadOrders() {
try {
const [ordersRes, vehiclesRes] = await Promise.all([
api.get(API_ENDPOINTS.ORDERS),
api.get(API_ENDPOINTS.VEHICLES)
]);
if (ordersRes.code === 200 && ordersRes.data) {
const myOrders = ordersRes.data.filter(o => o.customerId === currentCustomerId);
const vehicles = vehiclesRes.data || [];
const tbody = document.getElementById('ordersBody');
if (myOrders.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="empty-state">暂无维保记录</td></tr>';
return;
}
tbody.innerHTML = myOrders.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>¥${o.totalCost || 0}</td>
<td>${utils.getStatusBadge(o.status)}</td>
<td>${utils.formatDateTime(o.createTime)}</td>
</tr>
`;
}).join('');
}
} catch (error) {
console.error('加载工单失败:', error);
}
}
async function loadAppointments() {
try {
const [appointmentsRes, vehiclesRes] = await Promise.all([
api.get(API_ENDPOINTS.APPOINTMENTS),
api.get(API_ENDPOINTS.VEHICLES)
]);
if (appointmentsRes.code === 200 && appointmentsRes.data) {
const myAppointments = appointmentsRes.data.filter(a => a.customerId === currentCustomerId);
const vehicles = vehiclesRes.data || [];
const tbody = document.getElementById('appointmentsBody');
if (myAppointments.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">暂无预约记录</td></tr>';
return;
}
tbody.innerHTML = myAppointments.map(a => {
const vehicle = vehicles.find(v => v.vehicleId === a.vehicleId);
return `
<tr>
<td>${utils.getServiceTypeText(a.serviceType)}</td>
<td>${vehicle ? vehicle.licensePlate : '-'}</td>
<td>${utils.formatDateTime(a.appointmentTime)}</td>
<td>${utils.getStatusBadge(a.status)}</td>
<td>
${a.status === 'pending' ? `<button class="btn btn-danger" onclick="cancelAppointment(${a.appointmentId})">取消</button>` : '-'}
</td>
</tr>
`;
}).join('');
}
} catch (error) {
console.error('加载预约失败:', error);
}
}
async function loadVehicleOptions() {
try {
const response = await api.get(API_ENDPOINTS.VEHICLES);
if (response.code === 200 && response.data) {
const myVehicles = response.data.filter(v => v.customerId === currentCustomerId);
const select = document.getElementById('vehicleSelect');
select.innerHTML = '<option value="">请选择车辆</option>' +
myVehicles.map(v => `<option value="${v.vehicleId}">${v.licensePlate} - ${v.brand} ${v.model}</option>`).join('');
}
} catch (error) {
console.error('加载车辆选项失败:', error);
}
}
document.getElementById('appointmentForm').addEventListener('submit', async (e) => {
// 预约表单提交
document.getElementById('appointmentForm').addEventListener('submit', function(e) {
e.preventDefault();
const data = {
customerId: currentCustomerId,
vehicleId: parseInt(document.getElementById('vehicleSelect').value),
serviceType: document.getElementById('serviceType').value,
appointmentTime: document.getElementById('appointmentTime').value,
contactPhone: document.getElementById('contactPhone').value,
description: document.getElementById('description').value
};
try {
const response = await api.post(API_ENDPOINTS.APPOINTMENTS, data);
if (response.code === 200) {
utils.showSuccess('预约成功!');
document.getElementById('appointmentForm').reset();
loadAppointments();
} else {
utils.showError(response.message);
}
} catch (error) {
utils.showError('预约失败');
}
submitAppointment();
});
async function cancelAppointment(id) {
if (utils.confirm('确定要取消此预约吗?')) {
try {
const response = await api.put(API_ENDPOINTS.CANCEL_APPOINTMENT(id));
if (response.code === 200) {
utils.showSuccess('预约已取消');
loadAppointments();
} else {
utils.showError(response.message);
}
} catch (error) {
utils.showError('操作失败');
}
}
}
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';
}
</script>
</body>
</html>