用python实现一个小的系统
·
1.页面结构信息

2.页面部分的代码
kehu.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>电脑维修服务 | 客户信息登记系统</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: linear-gradient(145deg, #e9eef3 0%, #dce3ec 100%);
font-family: 'Inter', system-ui, sans-serif;
padding: 2rem 1.5rem;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.service-card {
max-width: 900px;
width: 100%;
background-color: #ffffff;
border-radius: 2rem;
box-shadow: 0 20px 35px -12px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.form-header {
background: #0b2b3b;
background-image: radial-gradient(circle at 10% 20%, #124c5f, #07212e);
padding: 1.8rem 2rem;
color: white;
}
.form-header h1 {
font-size: 1.9rem;
font-weight: 700;
display: flex;
align-items: center;
gap: 0.75rem;
}
.form-header h1 i { font-size: 2rem; color: #7ac7e0; }
.form-header p { margin-top: 0.6rem; font-size: 0.95rem; opacity: 0.85; }
.form-body { padding: 2rem; }
.input-group { margin-bottom: 1.6rem; display: flex; flex-direction: column; }
.input-group label { font-weight: 600; font-size: 0.9rem; margin-bottom: 0.5rem; color: #1e2f3e; display: flex; align-items: center; gap: 0.5rem; }
.required-star { color: #e03a3a; }
.optional-tag { font-size: 0.7rem; background-color: #eef2f6; color: #5e6f8d; padding: 0.2rem 0.5rem; border-radius: 20px; margin-left: 8px; }
input, textarea, select {
width: 100%;
padding: 0.85rem 1rem;
font-size: 0.95rem;
border: 1.5px solid #e2e8f0;
border-radius: 1rem;
transition: all 0.2s;
outline: none;
}
input:focus, textarea:focus { border-color: #2c7da0; box-shadow: 0 0 0 3px rgba(44, 125, 160, 0.2); }
input.error, textarea.error { border-color: #e03a3a !important; background-color: #fff6f5 !important; }
textarea { resize: vertical; min-height: 100px; }
.row-two { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; }
.hint-text { font-size: 0.7rem; color: #5e6f8d; margin-top: 0.35rem; }
.error-text { font-size: 0.7rem; color: #e03a3a; margin-top: 0.35rem; display: flex; align-items: center; gap: 4px; }
.action-buttons { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; margin-top: 2rem; }
.btn {
border: none;
padding: 0.85rem 1.6rem;
font-weight: 600;
font-size: 0.95rem;
border-radius: 2rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.6rem;
}
.btn-primary { background-color: #1f6e8c; color: white; }
.btn-primary:hover { background-color: #0e5a75; }
.btn-primary:disabled { background-color: #9bb7c4; cursor: not-allowed; }
.btn-secondary { background-color: #eef2f6; border: 1px solid #cbd5e1; }
.char-counter { font-size: 0.7rem; color: #7e8aa2; text-align: right; margin-top: 0.25rem; }
hr { margin: 1rem 0 0.5rem; border: 0; height: 1px; background: linear-gradient(to right, #e2e8f0, transparent); }
@media (max-width: 640px) {
body { padding: 1rem; }
.form-body { padding: 1.5rem; }
.row-two { grid-template-columns: 1fr; gap: 1rem; }
.action-buttons { flex-direction: column; }
.btn { justify-content: center; }
}
.info-note {
background: #f8fafc;
border-radius: 1rem;
padding: 0.8rem 1rem;
margin-top: 1rem;
font-size: 0.75rem;
color: #4a627a;
text-align: center;
border: 1px dashed #cbd5e1;
}
.toast-msg {
position: fixed;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
background-color: rgba(15, 35, 45, 0.95);
color: white;
padding: 0.9rem 1.6rem;
border-radius: 3rem;
display: flex;
align-items: center;
gap: 0.6rem;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.25s;
pointer-events: none;
}
.toast-msg.show { opacity: 1; visibility: visible; }
.toast-msg.success i { color: #6fcf97; }
.toast-msg.error i { color: #e67e7e; }
</style>
</head>
<body>
<div class="service-card">
<div class="form-header">
<h1><i class="fas fa-laptop-code"></i>电脑维修 · 客户信息登记</h1>
<p>请填写以下信息,带 <span style="color:#ffaaa5;">*</span> 为必填项,工程师将尽快与您联系。</p>
</div>
<div class="form-body">
<form id="repairForm">
<div class="row-two">
<div class="input-group">
<label><i class="fas fa-user"></i>客户姓名<span class="required-star">*</span></label>
<input type="text" id="customerName" name="customer_name" placeholder="例如:张明" maxlength="30">
<div class="hint-text">至少2个字符,最长30个字符</div>
<div id="nameError" class="error-text" style="display: none;"></div>
</div>
<div class="input-group">
<label><i class="fas fa-mobile-alt"></i>手机号码<span class="required-star">*</span></label>
<input type="tel" id="phone" name="phone" placeholder="11位手机号" maxlength="11">
<div class="hint-text">11位数字,以1开头</div>
<div id="phoneError" class="error-text" style="display: none;"></div>
</div>
</div>
<div class="input-group">
<label><i class="fas fa-desktop"></i>电脑型号<span class="required-star">*</span></label>
<input type="text" id="deviceModel" name="device_model" placeholder="例:联想拯救者Y7000 / MacBook Pro 2021">
<div id="modelError" class="error-text" style="display: none;"></div>
</div>
<div class="input-group">
<label><i class="fas fa-clipboard-list"></i>故障描述<span class="required-star">*</span></label>
<textarea id="faultDesc" name="fault_description" placeholder="请详细描述电脑出现的问题..." maxlength="1000"></textarea>
<div class="char-counter"><span id="faultCharCount">0</span> / 1000 字符</div>
<div class="hint-text">至少输入5个字符,越详细越好</div>
<div id="faultError" class="error-text" style="display: none;"></div>
</div>
<div><label style="font-weight:600; color:#2c7da0;"><i class="fas fa-chevron-circle-down"></i>更多信息(可选)</label><hr></div>
<div class="input-group">
<label><i class="fas fa-barcode"></i>电脑SN/序列号<span class="optional-tag">可选</span></label>
<input type="text" id="snCode" name="sn_code" placeholder="例: ABC123XYZ7890">
</div>
<div class="row-two">
<div class="input-group">
<label><i class="fas fa-envelope"></i>电子邮箱<span class="optional-tag">可选</span></label>
<input type="email" id="email" name="customer_email" placeholder="example@domain.com">
</div>
<div class="input-group">
<label><i class="fab fa-weixin"></i>微信号<span class="optional-tag">可选</span></label>
<input type="text" id="wechat" name="customer_wechat" placeholder="微信号码">
</div>
</div>
<div class="action-buttons">
<button type="button" class="btn btn-secondary" id="resetBtn"><i class="fas fa-undo-alt"></i>重置表单</button>
<button type="submit" class="btn btn-primary" id="submitBtn"><i class="fas fa-paper-plane"></i>提交维修申请</button>
</div>
<div class="info-note"><i class="fas fa-shield-alt"></i>信息仅用于维修服务,我们会严格保密。</div>
</form>
</div>
</div>
<div id="toastMsg" class="toast-msg"><i id="toastIcon" class="fas fa-check-circle"></i><span id="toastText">提示信息</span></div>
<script>
const form = document.getElementById('repairForm');
const nameInput = document.getElementById('customerName');
const phoneInput = document.getElementById('phone');
const modelInput = document.getElementById('deviceModel');
const faultInput = document.getElementById('faultDesc');
const resetBtn = document.getElementById('resetBtn');
const submitBtn = document.getElementById('submitBtn');
const toast = document.getElementById('toastMsg');
const toastIcon = document.getElementById('toastIcon');
const toastText = document.getElementById('toastText');
const faultCharCountSpan = document.getElementById('faultCharCount');
const nameErrorDiv = document.getElementById('nameError');
const phoneErrorDiv = document.getElementById('phoneError');
const modelErrorDiv = document.getElementById('modelError');
const faultErrorDiv = document.getElementById('faultError');
function showToast(message, isSuccess = true) {
toastText.innerText = message;
toastIcon.className = isSuccess ? 'fas fa-check-circle' : 'fas fa-exclamation-triangle';
toast.className = `toast-msg ${isSuccess ? 'success' : 'error'} show`;
setTimeout(() => toast.classList.remove('show'), 3500);
}
function resetFormFields() {
form.reset();
document.querySelectorAll('input, textarea').forEach(i => i.classList.remove('error'));
[nameErrorDiv, phoneErrorDiv, modelErrorDiv, faultErrorDiv].forEach(div => { div.style.display = 'none'; div.innerHTML = ''; });
updateFaultCharCount();
showToast('表单已重置', true);
}
function updateFaultCharCount() { faultCharCountSpan.innerText = faultInput.value.length; }
function validateNameField() {
const val = nameInput.value.trim();
if (!val) { showFieldError(nameInput, nameErrorDiv, '客户姓名不能为空'); return false; }
if (val.length < 2) { showFieldError(nameInput, nameErrorDiv, '姓名至少需要2个字符'); return false; }
clearFieldError(nameInput, nameErrorDiv);
return true;
}
function validatePhoneField() {
const val = phoneInput.value.trim();
if (!val) { showFieldError(phoneInput, phoneErrorDiv, '手机号码不能为空'); return false; }
if (!/^1[0-9]{10}$/.test(val)) { showFieldError(phoneInput, phoneErrorDiv, '手机号格式不正确(11位数字,以1开头)'); return false; }
clearFieldError(phoneInput, phoneErrorDiv);
return true;
}
function validateModelField() {
const val = modelInput.value.trim();
if (!val) { showFieldError(modelInput, modelErrorDiv, '电脑型号不能为空'); return false; }
clearFieldError(modelInput, modelErrorDiv);
return true;
}
function validateFaultField() {
const val = faultInput.value.trim();
if (!val) { showFieldError(faultInput, faultErrorDiv, '故障描述不能为空'); return false; }
if (val.length < 5) { showFieldError(faultInput, faultErrorDiv, '故障描述至少需要5个字符'); return false; }
clearFieldError(faultInput, faultErrorDiv);
return true;
}
function showFieldError(field, errorDiv, message) {
field.classList.add('error');
errorDiv.innerHTML = `<i class="fas fa-exclamation-circle"></i> ${message}`;
errorDiv.style.display = 'flex';
}
function clearFieldError(field, errorDiv) {
field.classList.remove('error');
errorDiv.style.display = 'none';
errorDiv.innerHTML = '';
}
function validateAllFields() {
return validateNameField() && validatePhoneField() && validateModelField() && validateFaultField();
}
nameInput.addEventListener('input', validateNameField);
phoneInput.addEventListener('input', function() { this.value = this.value.replace(/[^\d]/g, '').slice(0, 11); validatePhoneField(); });
modelInput.addEventListener('input', validateModelField);
faultInput.addEventListener('input', function() { updateFaultCharCount(); validateFaultField(); });
async function handleSubmit(e) {
e.preventDefault();
if (!validateAllFields()) { showToast('请正确填写所有必填项', false); return; }
const formData = new FormData(form);
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-pulse"></i> 提交中...';
submitBtn.disabled = true;
try {
const response = await fetch('/submit', { method: 'POST', body: formData });
const result = await response.json();
if (result.success) {
showToast(result.message, true);
setTimeout(resetFormFields, 1500);
} else {
showToast(result.message || '提交失败', false);
}
} catch (error) {
console.error('提交错误:', error);
showToast('网络连接失败,请检查网络后重试', false);
} finally {
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
}
}
form.addEventListener('submit', handleSubmit);
resetBtn.addEventListener('click', (e) => { e.preventDefault(); resetFormFields(); });
updateFaultCharCount();
</script>
</body>
</html>
维护客户的页面信息
2.2kehu-edit.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>客户数据管理 | 电脑维修系统</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: linear-gradient(145deg, #e9eef3 0%, #dce3ec 100%);
font-family: 'Inter', system-ui, sans-serif;
padding: 2rem;
min-height: 100vh;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
/* 头部 */
.header {
background: white;
border-radius: 1rem;
padding: 1.5rem 2rem;
margin-bottom: 1.5rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.header h1 {
font-size: 1.6rem;
color: #0b2b3b;
display: flex;
align-items: center;
gap: 0.8rem;
}
.header h1 i { color: #1f6e8c; }
.btn-add {
background: #1f6e8c;
color: white;
border: none;
padding: 0.7rem 1.5rem;
border-radius: 2rem;
cursor: pointer;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.2s;
}
.btn-add:hover { background: #0e5a75; transform: translateY(-1px); }
/* 搜索和筛选栏 */
.search-bar {
background: white;
border-radius: 1rem;
padding: 1.2rem 1.5rem;
margin-bottom: 1.5rem;
display: flex;
gap: 1rem;
flex-wrap: wrap;
align-items: flex-end;
}
.search-group {
flex: 1;
min-width: 180px;
}
.search-group label {
display: block;
font-size: 0.8rem;
font-weight: 600;
color: #4a627a;
margin-bottom: 0.4rem;
}
.search-group input, .search-group select {
width: 100%;
padding: 0.6rem 1rem;
border: 1.5px solid #e2e8f0;
border-radius: 0.8rem;
font-size: 0.9rem;
outline: none;
}
.search-group input:focus { border-color: #1f6e8c; }
.btn-search {
background: #2c7da0;
color: white;
border: none;
padding: 0.6rem 1.5rem;
border-radius: 0.8rem;
cursor: pointer;
font-weight: 500;
}
.btn-reset {
background: #eef2f6;
border: 1px solid #cbd5e1;
padding: 0.6rem 1.5rem;
border-radius: 0.8rem;
cursor: pointer;
}
/* 表格 */
.table-container {
background: white;
border-radius: 1rem;
overflow-x: auto;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background: #f8fafc;
padding: 1rem;
text-align: left;
font-weight: 600;
color: #1e2f3e;
border-bottom: 2px solid #e2e8f0;
}
td {
padding: 1rem;
border-bottom: 1px solid #eef2f6;
color: #334155;
}
tr:hover { background: #f8fafc; }
.action-btns {
display: flex;
gap: 0.5rem;
}
.btn-icon {
background: none;
border: none;
cursor: pointer;
font-size: 1.1rem;
padding: 0.3rem;
border-radius: 0.4rem;
transition: all 0.2s;
}
.btn-view { color: #2c7da0; }
.btn-view:hover { background: #e0f2fe; }
.btn-edit { color: #e67e22; }
.btn-edit:hover { background: #fee2d6; }
.btn-delete { color: #e03a3a; }
.btn-delete:hover { background: #fee2e2; }
/* 分页 */
.pagination {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
background: white;
border-radius: 1rem;
margin-top: 1rem;
flex-wrap: wrap;
gap: 1rem;
}
.page-size {
display: flex;
align-items: center;
gap: 0.5rem;
}
.page-size select {
padding: 0.4rem;
border-radius: 0.5rem;
border: 1px solid #cbd5e1;
}
.page-buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.page-btn {
padding: 0.4rem 0.8rem;
border: 1px solid #cbd5e1;
background: white;
border-radius: 0.5rem;
cursor: pointer;
}
.page-btn.active {
background: #1f6e8c;
color: white;
border-color: #1f6e8c;
}
.page-btn:hover:not(.active) { background: #eef2f6; }
/* 模态框 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
border-radius: 1.5rem;
max-width: 800px;
width: 90%;
max-height: 85vh;
overflow-y: auto;
padding: 2rem;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 2px solid #e2e8f0;
}
.modal-header h2 { color: #0b2b3b; }
.close-modal {
font-size: 1.8rem;
cursor: pointer;
color: #94a3b8;
}
.detail-grid {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 1rem;
margin-bottom: 1rem;
}
.detail-label {
font-weight: 600;
color: #475569;
}
.detail-value {
color: #1e293b;
word-break: break-all;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
font-weight: 600;
margin-bottom: 0.4rem;
color: #334155;
}
.form-group input, .form-group textarea, .form-group select {
width: 100%;
padding: 0.6rem;
border: 1.5px solid #e2e8f0;
border-radius: 0.6rem;
}
.btn-save {
background: #1f6e8c;
color: white;
padding: 0.7rem 1.5rem;
border: none;
border-radius: 0.6rem;
cursor: pointer;
margin-top: 1rem;
}
.toast-msg {
position: fixed;
bottom: 2rem;
right: 2rem;
background: #1f2f3e;
color: white;
padding: 0.8rem 1.5rem;
border-radius: 2rem;
z-index: 1100;
opacity: 0;
transition: opacity 0.3s;
}
.toast-msg.show { opacity: 1; }
.badge {
padding: 0.2rem 0.6rem;
border-radius: 1rem;
font-size: 0.7rem;
font-weight: 600;
}
.badge-pending { background: #fef3c7; color: #d97706; }
.badge-processing { background: #dbeafe; color: #2563eb; }
.badge-done { background: #dcfce7; color: #16a34a; }
.badge-cancelled { background: #fee2e2; color: #dc2626; }
@media (max-width: 768px) {
body { padding: 1rem; }
.detail-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-chart-line"></i> 客户数据管理中心</h1>
<button class="btn-add" onclick="openAddModal()"><i class="fas fa-plus"></i> 新增客户</button>
</div>
<div class="search-bar">
<div class="search-group">
<label>客户姓名</label>
<input type="text" id="searchName" placeholder="请输入姓名">
</div>
<div class="search-group">
<label>手机号</label>
<input type="text" id="searchPhone" placeholder="手机号">
</div>
<div class="search-group">
<label>维修状态</label>
<select id="searchStatus">
<option value="">全部</option>
<option value="1">待处理</option>
<option value="2">处理中</option>
<option value="3">已完成</option>
<option value="4">已取消</option>
<option value="5">待回访</option>
</select>
</div>
<button class="btn-search" onclick="loadData()"><i class="fas fa-search"></i> 搜索</button>
<button class="btn-reset" onclick="resetSearch()"><i class="fas fa-redo"></i> 重置</button>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>ID</th><th>客户姓名</th><th>手机号</th><th>电脑型号</th>
<th>故障描述(前20字)</th><th>状态</th><th>提交时间</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody">
<tr><td colspan="8" style="text-align:center">加载中...</td></tr>
</tbody>
</table>
</div>
<div class="pagination">
<div class="page-size">
<span>每页显示</span>
<select id="pageSize" onchange="changePageSize()">
<option value="10">10</option><option value="20">20</option>
<option value="30">30</option><option value="50">50</option>
</select>
<span>条</span>
</div>
<div class="page-buttons" id="pageButtons"></div>
<div><span id="totalInfo"></span></div>
</div>
</div>
<!-- 详情/编辑模态框 -->
<div id="detailModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">客户详情</h2>
<span class="close-modal" onclick="closeModal()">×</span>
</div>
<div id="modalBody"></div>
<button class="btn-save" id="saveBtn" onclick="saveCustomer()">保存修改</button>
</div>
</div>
<script>
let currentPage = 1;
let pageSize = 10;
let totalPages = 1;
let currentEditId = null;
let allData = [];
// 加载数据
async function loadData() {
const name = document.getElementById('searchName').value;
const phone = document.getElementById('searchPhone').value;
const status = document.getElementById('searchStatus').value;
try {
const response = await fetch(`/records?page=${currentPage}&size=${pageSize}&name=${encodeURIComponent(name)}&phone=${encodeURIComponent(phone)}&status=${status}`);
const result = await response.json();
if (result.success) {
allData = result.data;
renderTable(result.data);
totalPages = result.totalPages || Math.ceil(result.total / pageSize);
renderPagination();
document.getElementById('totalInfo').innerHTML = `共 ${result.total || 0} 条记录`;
} else {
showToast('加载失败', false);
}
} catch (error) {
showToast('网络错误', false);
}
}
function renderTable(data) {
const tbody = document.getElementById('tableBody');
if (!data || data.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" style="text-align:center">暂无数据</td></tr>';
return;
}
tbody.innerHTML = data.map(item => `
<tr>
<td>${item.id}</td>
<td><strong>${escapeHtml(item.customer_name)}</strong></td>
<td>${item.phone}</td>
<td>${escapeHtml(item.device_model || '-')}</td>
<td>${escapeHtml((item.fault_description || '').substring(0, 20))}${(item.fault_description || '').length > 20 ? '...' : ''}</td>
<td>${getStatusBadge(item.status)}</td>
<td>${item.submit_time ? new Date(item.submit_time).toLocaleString() : '-'}</td>
<td class="action-btns">
<button class="btn-icon btn-view" onclick="viewDetail(${item.id})" title="查看详情"><i class="fas fa-eye"></i></button>
<button class="btn-icon btn-edit" onclick="editCustomer(${item.id})" title="编辑"><i class="fas fa-edit"></i></button>
<button class="btn-icon btn-delete" onclick="deleteCustomer(${item.id})" title="删除"><i class="fas fa-trash"></i></button>
</td>
</tr>
`).join('');
}
function getStatusBadge(status) {
const map = {1:'待处理',2:'处理中',3:'已完成',4:'已取消',5:'待回访'};
const classMap = {1:'badge-pending',2:'badge-processing',3:'badge-done',4:'badge-cancelled',5:'badge-pending'};
return `<span class="badge ${classMap[status]}">${map[status] || '未知'}</span>`;
}
function renderPagination() {
const container = document.getElementById('pageButtons');
if (totalPages <= 1) {
container.innerHTML = '<button class="page-btn" disabled>1</button>';
return;
}
let html = '';
if (currentPage > 1) html += `<button class="page-btn" onclick="goPage(${currentPage-1})">上一页</button>`;
for (let i = 1; i <= totalPages; i++) {
if (i === 1 || i === totalPages || (i >= currentPage-2 && i <= currentPage+2)) {
html += `<button class="page-btn ${i === currentPage ? 'active' : ''}" onclick="goPage(${i})">${i}</button>`;
} else if (i === currentPage-3 || i === currentPage+3) {
html += `<button class="page-btn" disabled>...</button>`;
}
}
if (currentPage < totalPages) html += `<button class="page-btn" onclick="goPage(${currentPage+1})">下一页</button>`;
container.innerHTML = html;
}
function goPage(page) { currentPage = page; loadData(); }
function changePageSize() { pageSize = parseInt(document.getElementById('pageSize').value); currentPage = 1; loadData(); }
function resetSearch() { document.getElementById('searchName').value = ''; document.getElementById('searchPhone').value = ''; document.getElementById('searchStatus').value = ''; currentPage = 1; loadData(); }
// 查看详情
async function viewDetail(id) {
try {
const response = await fetch(`/record/${id}`);
const result = await response.json();
if (result.success) {
showDetailModal(result.data, 'view');
}
} catch (error) { showToast('加载失败', false); }
}
// 编辑客户
async function editCustomer(id) {
try {
const response = await fetch(`/record/${id}`);
const result = await response.json();
if (result.success) {
showDetailModal(result.data, 'edit');
}
} catch (error) { showToast('加载失败', false); }
}
// 新增客户
function openAddModal() {
currentEditId = null;
showDetailModal({
customer_name: '', phone: '', device_model: '', fault_description: '',
sn_code: '', customer_email: '', customer_wechat: '', status: 1,
emergency_level: '', source_channel: '', remark: ''
}, 'edit');
}
function showDetailModal(data, mode) {
const modal = document.getElementById('detailModal');
const modalTitle = document.getElementById('modalTitle');
const modalBody = document.getElementById('modalBody');
const saveBtn = document.getElementById('saveBtn');
if (mode === 'view') {
modalTitle.innerText = '客户详细信息';
saveBtn.style.display = 'none';
modalBody.innerHTML = `
<div class="detail-grid">
<div class="detail-label">ID:</div><div class="detail-value">${data.id}</div>
<div class="detail-label">客户姓名:</div><div class="detail-value">${escapeHtml(data.customer_name)}</div>
<div class="detail-label">手机号:</div><div class="detail-value">${data.phone}</div>
<div class="detail-label">电脑型号:</div><div class="detail-value">${escapeHtml(data.device_model || '-')}</div>
<div class="detail-label">电脑SN:</div><div class="detail-value">${escapeHtml(data.sn_code || '-')}</div>
<div class="detail-label">故障描述:</div><div class="detail-value">${escapeHtml(data.fault_description || '-')}</div>
<div class="detail-label">电子邮箱:</div><div class="detail-value">${escapeHtml(data.customer_email || '-')}</div>
<div class="detail-label">微信号:</div><div class="detail-value">${escapeHtml(data.customer_wechat || '-')}</div>
<div class="detail-label">紧急程度:</div><div class="detail-value">${getEmergencyText(data.emergency_level)}</div>
<div class="detail-label">维修状态:</div><div class="detail-value">${getStatusBadge(data.status)}</div>
<div class="detail-label">来源渠道:</div><div class="detail-value">${escapeHtml(data.source_channel || '-')}</div>
<div class="detail-label">指派工程师:</div><div class="detail-value">${escapeHtml(data.assign_engineer || '-')}</div>
<div class="detail-label">维修费用:</div><div class="detail-value">${data.repair_cost ? '¥' + data.repair_cost : '-'}</div>
<div class="detail-label">维修结果:</div><div class="detail-value">${escapeHtml(data.repair_result || '-')}</div>
<div class="detail-label">备注:</div><div class="detail-value">${escapeHtml(data.remark || '-')}</div>
<div class="detail-label">提交时间:</div><div class="detail-value">${data.submit_time ? new Date(data.submit_time).toLocaleString() : '-'}</div>
<div class="detail-label">更新时间:</div><div class="detail-value">${data.update_time ? new Date(data.update_time).toLocaleString() : '-'}</div>
</div>
`;
} else {
modalTitle.innerText = currentEditId ? '编辑客户信息' : '新增客户';
saveBtn.style.display = 'block';
currentEditId = data.id || null;
modalBody.innerHTML = `
<div class="form-group"><label>客户姓名 *</label><input type="text" id="edit_name" value="${escapeHtml(data.customer_name || '')}"></div>
<div class="form-group"><label>手机号 *</label><input type="text" id="edit_phone" value="${data.phone || ''}"></div>
<div class="form-group"><label>电脑型号 *</label><input type="text" id="edit_model" value="${escapeHtml(data.device_model || '')}"></div>
<div class="form-group"><label>故障描述 *</label><textarea id="edit_fault" rows="3">${escapeHtml(data.fault_description || '')}</textarea></div>
<div class="form-group"><label>电脑SN</label><input type="text" id="edit_sn" value="${escapeHtml(data.sn_code || '')}"></div>
<div class="form-group"><label>电子邮箱</label><input type="email" id="edit_email" value="${escapeHtml(data.customer_email || '')}"></div>
<div class="form-group"><label>微信号</label><input type="text" id="edit_wechat" value="${escapeHtml(data.customer_wechat || '')}"></div>
<div class="form-group"><label>维修状态</label><select id="edit_status">
<option value="1" ${data.status == 1 ? 'selected' : ''}>待处理</option>
<option value="2" ${data.status == 2 ? 'selected' : ''}>处理中</option>
<option value="3" ${data.status == 3 ? 'selected' : ''}>已完成</option>
<option value="4" ${data.status == 4 ? 'selected' : ''}>已取消</option>
<option value="5" ${data.status == 5 ? 'selected' : ''}>待回访</option>
</select></div>
<div class="form-group"><label>紧急程度</label><select id="edit_emergency">
<option value="">请选择</option>
<option value="1" ${data.emergency_level == 1 ? 'selected' : ''}>低</option>
<option value="2" ${data.emergency_level == 2 ? 'selected' : ''}>中</option>
<option value="3" ${data.emergency_level == 3 ? 'selected' : ''}>高</option>
<option value="4" ${data.emergency_level == 4 ? 'selected' : ''}>紧急</option>
</select></div>
<div class="form-group"><label>来源渠道</label><input type="text" id="edit_source" value="${escapeHtml(data.source_channel || '')}"></div>
<div class="form-group"><label>指派工程师</label><input type="text" id="edit_engineer" value="${escapeHtml(data.assign_engineer || '')}"></div>
<div class="form-group"><label>维修费用</label><input type="number" id="edit_cost" step="0.01" value="${data.repair_cost || ''}"></div>
<div class="form-group"><label>维修结果</label><textarea id="edit_result" rows="2">${escapeHtml(data.repair_result || '')}</textarea></div>
<div class="form-group"><label>备注</label><textarea id="edit_remark" rows="2">${escapeHtml(data.remark || '')}</textarea></div>
`;
}
modal.style.display = 'flex';
}
async function saveCustomer() {
const formData = {
customer_name: document.getElementById('edit_name').value.trim(),
phone: document.getElementById('edit_phone').value.trim(),
device_model: document.getElementById('edit_model').value.trim(),
fault_description: document.getElementById('edit_fault').value.trim(),
sn_code: document.getElementById('edit_sn').value || null,
customer_email: document.getElementById('edit_email').value || null,
customer_wechat: document.getElementById('edit_wechat').value || null,
status: parseInt(document.getElementById('edit_status').value),
emergency_level: document.getElementById('edit_emergency').value || null,
source_channel: document.getElementById('edit_source').value || null,
assign_engineer: document.getElementById('edit_engineer').value || null,
repair_cost: document.getElementById('edit_cost').value || null,
repair_result: document.getElementById('edit_result').value || null,
remark: document.getElementById('edit_remark').value || null
};
if (!formData.customer_name || !formData.phone || !formData.device_model || !formData.fault_description) {
showToast('请填写所有必填项', false); return;
}
if (!/^1[0-9]{10}$/.test(formData.phone)) { showToast('手机号格式不正确', false); return; }
const url = currentEditId ? `/record/${currentEditId}` : '/record';
const method = currentEditId ? 'PUT' : 'POST';
try {
const response = await fetch(url, { method, headers: {'Content-Type':'application/json'}, body: JSON.stringify(formData) });
const result = await response.json();
if (result.success) {
showToast(result.message, true);
closeModal();
loadData();
} else { showToast(result.message || '操作失败', false); }
} catch (error) { showToast('网络错误', false); }
}
async function deleteCustomer(id) {
if (!confirm('确定要删除这条记录吗?')) return;
try {
const response = await fetch(`/record/${id}`, { method: 'DELETE' });
const result = await response.json();
if (result.success) { showToast('删除成功', true); loadData(); }
else { showToast('删除失败', false); }
} catch (error) { showToast('网络错误', false); }
}
function closeModal() { document.getElementById('detailModal').style.display = 'none'; }
function showToast(msg, success) {
const toast = document.createElement('div');
toast.className = 'toast-msg';
toast.innerHTML = `<i class="fas ${success ? 'fa-check-circle' : 'fa-exclamation-circle'}"></i> ${msg}`;
document.body.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }, 3000);
}
function getEmergencyText(level) {
const map = {1:'低',2:'中',3:'高',4:'紧急'};
return map[level] || '-';
}
function escapeHtml(str) { if(!str) return ''; return str.replace(/[&<>]/g, function(m){if(m==='&') return '&'; if(m==='<') return '<'; if(m==='>') return '>'; return m;}); }
loadData();
</script>
</body>
</html>
3.主体功能部分的代码 kehu.py
# kehu.py
import mysql.connector
from mysql.connector import Error
import re
from flask import Flask, request, jsonify, render_template
app = Flask(__name__)
# 数据库配置
DB_CONFIG = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': 'root',
'database': 'test',
'charset': 'utf8'
}
class KehuDB:
"""客户信息数据库操作类"""
def __init__(self):
self.conn = None
self.cursor = None
def connect(self):
try:
self.conn = mysql.connector.connect(**DB_CONFIG)
self.cursor = self.conn.cursor(dictionary=True)
return True
except Error as e:
print(f"数据库连接失败: {e}")
return False
def close(self):
if self.cursor:
self.cursor.close()
if self.conn:
self.conn.close()
def insert_customer(self, data):
if not self.connect():
return False, "数据库连接失败", None
try:
sql = """
INSERT INTO kehu (
customer_name, phone, device_model, fault_description,
sn_code, customer_email, customer_qq, customer_wechat,
status, submit_time, update_time
) VALUES (
%(customer_name)s, %(phone)s, %(device_model)s, %(fault_description)s,
%(sn_code)s, %(customer_email)s, %(customer_qq)s, %(customer_wechat)s,
1, NOW(), NOW()
)
"""
self.cursor.execute(sql, data)
self.conn.commit()
insert_id = self.cursor.lastrowid
return True, "数据插入成功", insert_id
except Error as e:
self.conn.rollback()
print(f"数据库错误详情: {e}")
return False, f"数据库错误: {e}", None
except Exception as e:
self.conn.rollback()
print(f"错误详情: {e}")
return False, f"错误: {e}", None
finally:
self.close()
def get_all_records(self, limit=100):
if not self.connect():
return []
try:
sql = "SELECT * FROM kehu ORDER BY submit_time DESC LIMIT %s"
self.cursor.execute(sql, (limit,))
return self.cursor.fetchall()
except Error as e:
print(f"查询失败: {e}")
return []
finally:
self.close()
def get_record_by_id(self, record_id):
if not self.connect():
return None
try:
sql = "SELECT * FROM kehu WHERE id = %s"
self.cursor.execute(sql, (record_id,))
return self.cursor.fetchone()
except Error as e:
print(f"查询失败: {e}")
return None
finally:
self.close()
def get_records_by_phone(self, phone):
if not self.connect():
return []
try:
sql = "SELECT * FROM kehu WHERE phone = %s ORDER BY submit_time DESC"
self.cursor.execute(sql, (phone,))
return self.cursor.fetchall()
except Error as e:
print(f"查询失败: {e}")
return []
finally:
self.close()
def update_status(self, record_id, status):
if not self.connect():
return False
try:
sql = "UPDATE kehu SET status = %s, update_time = NOW() WHERE id = %s"
self.cursor.execute(sql, (status, record_id))
self.conn.commit()
return True
except Error as e:
print(f"更新失败: {e}")
self.conn.rollback()
return False
finally:
self.close()
# ========== 新增方法 ==========
def get_records_paginated(self, page, size, name, phone, status):
"""分页查询记录(支持搜索)"""
if not self.connect():
return [], 0
try:
# 构建查询条件
conditions = []
params = []
if name:
conditions.append("customer_name LIKE %s")
params.append(f"%{name}%")
if phone:
conditions.append("phone LIKE %s")
params.append(f"%{phone}%")
if status:
conditions.append("status = %s")
params.append(status)
where_clause = " WHERE " + " AND ".join(conditions) if conditions else ""
# 查询总数
count_sql = f"SELECT COUNT(*) as total FROM kehu{where_clause}"
self.cursor.execute(count_sql, params)
total = self.cursor.fetchone()['total']
# 查询分页数据
offset = (page - 1) * size
sql = f"SELECT * FROM kehu{where_clause} ORDER BY submit_time DESC LIMIT %s OFFSET %s"
self.cursor.execute(sql, params + [size, offset])
records = self.cursor.fetchall()
return records, total
except Error as e:
print(f"查询失败: {e}")
return [], 0
finally:
self.close()
def create_customer(self, data):
"""新增客户(完整版)"""
if not self.connect():
return False, "数据库连接失败", None
try:
sql = """
INSERT INTO kehu (
customer_name, phone, device_model, fault_description,
sn_code, customer_email, customer_qq, customer_wechat,
emergency_level, source_channel, assign_engineer,
repair_cost, repair_result, remark, status, submit_time, update_time
) VALUES (
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW()
)
"""
values = (
data.get('customer_name'), data.get('phone'), data.get('device_model'), data.get('fault_description'),
data.get('sn_code'), data.get('customer_email'), data.get('customer_qq'), data.get('customer_wechat'),
data.get('emergency_level'), data.get('source_channel'), data.get('assign_engineer'),
data.get('repair_cost'), data.get('repair_result'), data.get('remark'),
data.get('status', 1)
)
self.cursor.execute(sql, values)
self.conn.commit()
insert_id = self.cursor.lastrowid
return True, "添加成功", insert_id
except Error as e:
self.conn.rollback()
print(f"数据库错误详情: {e}")
return False, f"数据库错误: {e}", None
except Exception as e:
self.conn.rollback()
print(f"错误详情: {e}")
return False, f"错误: {e}", None
finally:
self.close()
def update_customer(self, record_id, data):
"""更新客户(完整版)"""
if not self.connect():
return False, "数据库连接失败"
try:
sql = """
UPDATE kehu SET
customer_name=%s, phone=%s, device_model=%s, fault_description=%s,
sn_code=%s, customer_email=%s, customer_qq=%s, customer_wechat=%s,
status=%s, emergency_level=%s, source_channel=%s, assign_engineer=%s,
repair_cost=%s, repair_result=%s, remark=%s, update_time=NOW()
WHERE id=%s
"""
values = (
data.get('customer_name'), data.get('phone'), data.get('device_model'), data.get('fault_description'),
data.get('sn_code'), data.get('customer_email'), data.get('customer_qq'), data.get('customer_wechat'),
data.get('status', 1), data.get('emergency_level'), data.get('source_channel'),
data.get('assign_engineer'),
data.get('repair_cost'), data.get('repair_result'), data.get('remark'), record_id
)
self.cursor.execute(sql, values)
self.conn.commit()
return True, "更新成功"
except Error as e:
self.conn.rollback()
print(f"更新失败: {e}")
return False, f"数据库错误: {e}"
finally:
self.close()
def delete_customer(self, record_id):
"""删除客户"""
if not self.connect():
return False, "数据库连接失败"
try:
sql = "DELETE FROM kehu WHERE id = %s"
self.cursor.execute(sql, (record_id,))
self.conn.commit()
return True, "删除成功"
except Error as e:
self.conn.rollback()
print(f"删除失败: {e}")
return False, f"删除失败: {e}"
finally:
self.close()
def validate_form_data(data):
errors = {}
name = data.get('customer_name', '').strip()
if not name:
errors['customer_name'] = '客户姓名不能为空'
elif len(name) < 2 or len(name) > 30:
errors['customer_name'] = '姓名长度应为2-30个字符'
phone = data.get('phone', '').strip()
phone_pattern = re.compile(r'^1[0-9]{10}$')
if not phone:
errors['phone'] = '手机号码不能为空'
elif not phone_pattern.match(phone):
errors['phone'] = '手机号格式不正确(11位数字,以1开头)'
model = data.get('device_model', '').strip()
if not model:
errors['device_model'] = '电脑型号不能为空'
fault = data.get('fault_description', '').strip()
if not fault:
errors['fault_description'] = '故障描述不能为空'
elif len(fault) < 5:
errors['fault_description'] = '故障描述至少需要5个字符'
return errors
# ======================================================
# Flask 路由定义
# ======================================================
@app.route('/')
def index():
"""显示表单页面"""
return render_template('kehu.html')
@app.route('/edit')
def edit_page():
"""客户管理页面"""
return render_template('kehu-edit.html')
@app.route('/submit', methods=['POST'])
def submit():
"""处理表单提交"""
print("收到提交请求")
print("表单数据:", request.form)
form_data = {
'customer_name': request.form.get('customer_name', '').strip(),
'phone': request.form.get('phone', '').strip(),
'device_model': request.form.get('device_model', '').strip(),
'fault_description': request.form.get('fault_description', '').strip(),
'sn_code': request.form.get('sn_code') or None,
'customer_email': request.form.get('customer_email') or None,
'customer_qq': request.form.get('customer_qq') or None,
'customer_wechat': request.form.get('customer_wechat') or None,
}
errors = validate_form_data(form_data)
if errors:
return jsonify({'success': False, 'errors': errors, 'message': '验证失败'}), 400
db = KehuDB()
success, message, insert_id = db.insert_customer(form_data)
if success:
return jsonify({'success': True, 'message': '维修申请提交成功!', 'id': insert_id})
else:
return jsonify({'success': False, 'message': message}), 500
@app.route('/records', methods=['GET'])
def get_records():
"""获取分页记录(支持搜索)"""
page = int(request.args.get('page', 1))
size = int(request.args.get('size', 10))
name = request.args.get('name', '')
phone = request.args.get('phone', '')
status = request.args.get('status', '')
db = KehuDB()
records, total = db.get_records_paginated(page, size, name, phone, status)
return jsonify({
'success': True,
'data': records,
'total': total,
'page': page,
'size': size,
'totalPages': (total + size - 1) // size
})
@app.route('/record/<int:record_id>', methods=['GET'])
def get_record(record_id):
db = KehuDB()
record = db.get_record_by_id(record_id)
if record:
return jsonify({'success': True, 'data': record})
return jsonify({'success': False, 'message': '记录不存在'}), 404
@app.route('/record', methods=['POST'])
def create_record():
"""新增客户"""
data = request.get_json()
# 验证必填字段
if not data.get('customer_name') or not data.get('phone') or not data.get('device_model') or not data.get(
'fault_description'):
return jsonify({'success': False, 'message': '请填写所有必填项'}), 400
if not re.match(r'^1[0-9]{10}$', data.get('phone', '')):
return jsonify({'success': False, 'message': '手机号格式不正确'}), 400
db = KehuDB()
success, message, insert_id = db.create_customer(data)
if success:
return jsonify({'success': True, 'message': message, 'id': insert_id})
return jsonify({'success': False, 'message': message}), 500
@app.route('/record/<int:record_id>', methods=['PUT'])
def update_record(record_id):
"""更新客户(完整版)"""
data = request.get_json()
# 如果只是更新状态(兼容旧接口)
if 'status' in data and len(data) == 1:
status = data.get('status')
if not status or status not in [1, 2, 3, 4, 5]:
return jsonify({'success': False, 'message': '无效的状态值'}), 400
db = KehuDB()
success = db.update_status(record_id, status)
if success:
return jsonify({'success': True, 'message': '状态更新成功'})
return jsonify({'success': False, 'message': '更新失败'}), 500
# 完整更新
db = KehuDB()
success, message = db.update_customer(record_id, data)
if success:
return jsonify({'success': True, 'message': message})
return jsonify({'success': False, 'message': message}), 500
@app.route('/record/<int:record_id>', methods=['DELETE'])
def delete_record(record_id):
"""删除记录"""
db = KehuDB()
success, message = db.delete_customer(record_id)
if success:
return jsonify({'success': True, 'message': message})
return jsonify({'success': False, 'message': message}), 500
@app.route('/search', methods=['GET'])
def search_by_phone():
phone = request.args.get('phone', '')
if not phone:
return jsonify({'success': False, 'message': '请提供手机号'}), 400
db = KehuDB()
records = db.get_records_by_phone(phone)
return jsonify({'success': True, 'data': records})
@app.route('/test-db')
def test_db():
"""测试数据库连接"""
db = KehuDB()
if db.connect():
db.close()
return jsonify({'success': True, 'message': '数据库连接成功'})
return jsonify({'success': False, 'message': '数据库连接失败'}), 500
if __name__ == '__main__':
print("=" * 50)
print("启动服务器...")
print("访问地址: http://localhost:5000")
print("客户登记: http://localhost:5000")
print("客户管理: http://localhost:5000/edit")
print("测试数据库: http://localhost:5000/test-db")
print("=" * 50)
app.run(debug=True, host='0.0.0.0', port=5000)
上述部分就可以运行
-- ======================================================
-- 表名: kehu (客户维修信息表)
-- 描述: 存储电脑维修客户的个人信息及设备故障记录
-- 必填字段: 客户姓名、手机号、电脑型号、故障描述
-- 作者: 自动生成
-- 日期: 2026-04-19
-- ======================================================
DROP TABLE IF EXISTS `kehu`;
CREATE TABLE `kehu` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '记录ID,主键,自增',
`customer_name` VARCHAR(50) NOT NULL COMMENT '客户姓名,必填,2-30个字符',
`phone` CHAR(11) NOT NULL COMMENT '手机号码,必填,11位数字,格式:1开头的11位数字',
`device_model` VARCHAR(100) NOT NULL COMMENT '电脑型号,必填,如:联想拯救者Y7000、MacBook Pro 2021',
`fault_description` TEXT NOT NULL COMMENT '故障描述,必填,详细说明电脑问题,至少5个字符',
`sn_code` VARCHAR(50) DEFAULT NULL COMMENT '电脑序列号(SN),可选,设备唯一标识',
`customer_email` VARCHAR(100) DEFAULT NULL COMMENT '电子邮箱,可选,用于发送维修进度',
`customer_qq` VARCHAR(20) DEFAULT NULL COMMENT 'QQ号码,可选,备用联系方式',
`customer_wechat` VARCHAR(50) DEFAULT NULL COMMENT '微信号,可选,便于沟通',
`appointment_time` DATETIME DEFAULT NULL COMMENT '预约上门时间,可选',
`device_accessories` VARCHAR(200) DEFAULT NULL COMMENT '附带配件,可选,如:电源适配器、鼠标等',
`purchase_date` DATE DEFAULT NULL COMMENT '购买日期,可选,用于判断是否在保修期',
`is_under_warranty` TINYINT(1) DEFAULT NULL COMMENT '是否在保修期内,可选,0-否,1-是,NULL-未知',
`emergency_level` TINYINT UNSIGNED DEFAULT NULL COMMENT '紧急程度,可选,1-低,2-中,3-高,4-紧急',
`source_channel` VARCHAR(50) DEFAULT NULL COMMENT '来源渠道,可选,如:网站、门店、电话推荐',
`status` TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '维修状态:1-待处理,2-处理中,3-已完成,4-已取消,5-待回访',
`submit_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '提交时间,记录客户提交申请的时间',
`update_time` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '更新时间,记录最后修改时间',
`assign_engineer` VARCHAR(50) DEFAULT NULL COMMENT '指派工程师姓名,可选',
`repair_cost` DECIMAL(10,2) DEFAULT NULL COMMENT '维修费用,可选,单位:元,精确到分',
`repair_result` VARCHAR(500) DEFAULT NULL COMMENT '维修结果说明,可选',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '内部备注,可选,员工可记录额外信息',
PRIMARY KEY (`id`),
KEY `idx_phone` (`phone`),
KEY `idx_customer_name` (`customer_name`),
KEY `idx_status` (`status`),
KEY `idx_submit_time` (`submit_time`),
KEY `idx_device_model` (`device_model`),
KEY `idx_sn_code` (`sn_code`),
KEY `idx_appointment_time` (`appointment_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='电脑维修客户信息表';
-- ======================================================
-- 插入测试数据(示例)
-- ======================================================
-- 示例1:完整填写所有字段
INSERT INTO `kehu` (
`customer_name`,
`phone`,
`device_model`,
`fault_description`,
`sn_code`,
`customer_email`,
`customer_qq`,
`customer_wechat`,
`appointment_time`,
`device_accessories`,
`purchase_date`,
`is_under_warranty`,
`emergency_level`,
`source_channel`,
`status`
) VALUES (
'张明',
'13812345678',
'联想 拯救者Y7000',
'电脑无法开机,按电源键无任何反应,电源适配器指示灯正常,已尝试更换插座无效',
'ABC123XYZ7890',
'zhangming@example.com',
'123456789',
'zm_wx123',
'2026-04-20 14:00:00',
'电源适配器、鼠标',
'2025-01-15',
1, -- 在保修期内
3, -- 紧急程度:高
'官方网站',
1 -- 待处理
);
-- 示例2:仅填写必填字段(客户姓名、手机号、电脑型号、故障描述)
INSERT INTO `kehu` (
`customer_name`,
`phone`,
`device_model`,
`fault_description`
) VALUES (
'李芳',
'15987654321',
'MacBook Pro 2021 14寸',
'屏幕出现闪烁条纹,外接显示器正常,疑似排线问题,需要检测'
);
-- 示例3:另一个完整示例
INSERT INTO `kehu` (
`customer_name`,
`phone`,
`device_model`,
`fault_description`,
`sn_code`,
`emergency_level`,
`status`
) VALUES (
'王伟',
'18655556666',
'戴尔 XPS 15 9520',
'频繁蓝屏,错误代码0x0000007B,已重装系统仍出现,怀疑硬盘故障',
'DELL2026SN001',
2, -- 紧急程度:中
2 -- 处理中
);
-- ======================================================
-- 常用查询示例
-- ======================================================
-- 1. 查询今日新增的维修申请(仅显示必填字段)
SELECT `id`, `customer_name`, `phone`, `device_model`, `fault_description`, `submit_time`
FROM `kehu`
WHERE DATE(`submit_time`) = CURDATE()
ORDER BY `submit_time` DESC;
-- 2. 按手机号查询客户历史维修记录
SELECT * FROM `kehu` WHERE `phone` = '13812345678' ORDER BY `submit_time` DESC;
-- 3. 统计各电脑型号的故障数量
SELECT `device_model`, COUNT(*) as fault_count
FROM `kehu`
GROUP BY `device_model`
ORDER BY fault_count DESC;
-- 4. 查询紧急程度高的待处理工单
SELECT * FROM `kehu`
WHERE `status` = 1 AND `emergency_level` >= 3
ORDER BY `emergency_level` DESC, `submit_time` ASC;
-- ======================================================
-- 说明文档
-- ======================================================
/*
【必填字段说明】(NOT NULL)
1. customer_name - 客户姓名,长度2-30字符
2. phone - 手机号,11位,格式校验:1开头
3. device_model - 电脑型号,自由文本
4. fault_description - 故障描述,至少5个字符
【可选字段说明】(DEFAULT NULL)
1. sn_code - 序列号(建议填写,便于保修查询)
2. customer_email - 电子邮箱
3. customer_qq - QQ号
4. customer_wechat - 微信号
5. appointment_time - 预约时间
6. device_accessories - 附带配件
7. purchase_date - 购买日期
8. is_under_warranty - 是否在保修期(0-否,1-是)
9. emergency_level - 紧急程度(1-低,2-中,3-高,4-紧急)
10. source_channel - 来源渠道
【状态枚举】
1 - 待处理
2 - 处理中
3 - 已完成
4 - 已取消
5 - 待回访
【建表后验证】
-- 查看表结构
DESC kehu;
-- 查看约束
SHOW CREATE TABLE kehu;
-- 测试必填字段(以下插入应失败,因为缺少必填字段)
-- INSERT INTO `kehu` (`customer_name`) VALUES ('测试'); -- 会报错
*/
truncate table test.kehu;
上述代码 时间上有点问题

建议先修改后端代码,因为问题根源是数据库返回的时间格式不正确。
kehu.py
# kehu.py
import mysql.connector
from mysql.connector import Error
import re
from flask import Flask, request, jsonify, render_template
app = Flask(__name__)
# 数据库配置
DB_CONFIG = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': 'root',
'database': 'test',
'charset': 'utf8'
}
class KehuDB:
"""客户信息数据库操作类"""
def __init__(self):
self.conn = None
self.cursor = None
def connect(self):
try:
self.conn = mysql.connector.connect(**DB_CONFIG)
self.cursor = self.conn.cursor(dictionary=True)
return True
except Error as e:
print(f"数据库连接失败: {e}")
return False
def close(self):
if self.cursor:
self.cursor.close()
if self.conn:
self.conn.close()
def insert_customer(self, data):
if not self.connect():
return False, "数据库连接失败", None
try:
sql = """
INSERT INTO kehu (
customer_name, phone, device_model, fault_description,
sn_code, customer_email, customer_qq, customer_wechat,
status, submit_time, update_time
) VALUES (
%(customer_name)s, %(phone)s, %(device_model)s, %(fault_description)s,
%(sn_code)s, %(customer_email)s, %(customer_qq)s, %(customer_wechat)s,
1, NOW(), NOW()
)
"""
self.cursor.execute(sql, data)
self.conn.commit()
insert_id = self.cursor.lastrowid
return True, "数据插入成功", insert_id
except Error as e:
self.conn.rollback()
print(f"数据库错误详情: {e}")
return False, f"数据库错误: {e}", None
except Exception as e:
self.conn.rollback()
print(f"错误详情: {e}")
return False, f"错误: {e}", None
finally:
self.close()
def get_all_records(self, limit=100):
if not self.connect():
return []
try:
sql = "SELECT * FROM kehu ORDER BY submit_time DESC LIMIT %s"
self.cursor.execute(sql, (limit,))
return self.cursor.fetchall()
except Error as e:
print(f"查询失败: {e}")
return []
finally:
self.close()
def get_record_by_id(self, record_id):
if not self.connect():
return None
try:
sql = "SELECT * FROM kehu WHERE id = %s"
self.cursor.execute(sql, (record_id,))
return self.cursor.fetchone()
except Error as e:
print(f"查询失败: {e}")
return None
finally:
self.close()
def get_records_by_phone(self, phone):
if not self.connect():
return []
try:
sql = "SELECT * FROM kehu WHERE phone = %s ORDER BY submit_time DESC"
self.cursor.execute(sql, (phone,))
return self.cursor.fetchall()
except Error as e:
print(f"查询失败: {e}")
return []
finally:
self.close()
def update_status(self, record_id, status):
if not self.connect():
return False
try:
sql = "UPDATE kehu SET status = %s, update_time = NOW() WHERE id = %s"
self.cursor.execute(sql, (status, record_id))
self.conn.commit()
return True
except Error as e:
print(f"更新失败: {e}")
self.conn.rollback()
return False
finally:
self.close()
# ========== 新增方法 ==========
def get_records_paginated(self, page, size, name, phone, status):
"""分页查询记录(支持搜索)"""
if not self.connect():
return [], 0
try:
# 构建查询条件
conditions = []
params = []
if name:
conditions.append("customer_name LIKE %s")
params.append(f"%{name}%")
if phone:
conditions.append("phone LIKE %s")
params.append(f"%{phone}%")
if status:
conditions.append("status = %s")
params.append(status)
where_clause = " WHERE " + " AND ".join(conditions) if conditions else ""
# 查询总数
count_sql = f"SELECT COUNT(*) as total FROM kehu{where_clause}"
self.cursor.execute(count_sql, params)
total = self.cursor.fetchone()['total']
# 查询分页数据 - 保持原样,不做任何修改
offset = (page - 1) * size
sql = f"SELECT * FROM kehu{where_clause} ORDER BY submit_time DESC LIMIT %s OFFSET %s"
self.cursor.execute(sql, params + [size, offset])
records = self.cursor.fetchall()
return records, total
except Error as e:
print(f"查询失败: {e}")
return [], 0
finally:
self.close()
def create_customer(self, data):
"""新增客户(完整版)"""
if not self.connect():
return False, "数据库连接失败", None
try:
sql = """
INSERT INTO kehu (
customer_name, phone, device_model, fault_description,
sn_code, customer_email, customer_qq, customer_wechat,
emergency_level, source_channel, assign_engineer,
repair_cost, repair_result, remark, status, submit_time, update_time
) VALUES (
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW()
)
"""
values = (
data.get('customer_name'), data.get('phone'), data.get('device_model'), data.get('fault_description'),
data.get('sn_code'), data.get('customer_email'), data.get('customer_qq'), data.get('customer_wechat'),
data.get('emergency_level'), data.get('source_channel'), data.get('assign_engineer'),
data.get('repair_cost'), data.get('repair_result'), data.get('remark'),
data.get('status', 1)
)
self.cursor.execute(sql, values)
self.conn.commit()
insert_id = self.cursor.lastrowid
return True, "添加成功", insert_id
except Error as e:
self.conn.rollback()
print(f"数据库错误详情: {e}")
return False, f"数据库错误: {e}", None
except Exception as e:
self.conn.rollback()
print(f"错误详情: {e}")
return False, f"错误: {e}", None
finally:
self.close()
def update_customer(self, record_id, data):
"""更新客户(完整版)"""
if not self.connect():
return False, "数据库连接失败"
try:
sql = """
UPDATE kehu SET
customer_name=%s, phone=%s, device_model=%s, fault_description=%s,
sn_code=%s, customer_email=%s, customer_qq=%s, customer_wechat=%s,
status=%s, emergency_level=%s, source_channel=%s, assign_engineer=%s,
repair_cost=%s, repair_result=%s, remark=%s, update_time=NOW()
WHERE id=%s
"""
values = (
data.get('customer_name'), data.get('phone'), data.get('device_model'), data.get('fault_description'),
data.get('sn_code'), data.get('customer_email'), data.get('customer_qq'), data.get('customer_wechat'),
data.get('status', 1), data.get('emergency_level'), data.get('source_channel'),
data.get('assign_engineer'),
data.get('repair_cost'), data.get('repair_result'), data.get('remark'), record_id
)
self.cursor.execute(sql, values)
self.conn.commit()
return True, "更新成功"
except Error as e:
self.conn.rollback()
print(f"更新失败: {e}")
return False, f"数据库错误: {e}"
finally:
self.close()
def delete_customer(self, record_id):
"""删除客户"""
if not self.connect():
return False, "数据库连接失败"
try:
sql = "DELETE FROM kehu WHERE id = %s"
self.cursor.execute(sql, (record_id,))
self.conn.commit()
return True, "删除成功"
except Error as e:
self.conn.rollback()
print(f"删除失败: {e}")
return False, f"删除失败: {e}"
finally:
self.close()
def validate_form_data(data):
errors = {}
name = data.get('customer_name', '').strip()
if not name:
errors['customer_name'] = '客户姓名不能为空'
elif len(name) < 2 or len(name) > 30:
errors['customer_name'] = '姓名长度应为2-30个字符'
phone = data.get('phone', '').strip()
phone_pattern = re.compile(r'^1[0-9]{10}$')
if not phone:
errors['phone'] = '手机号码不能为空'
elif not phone_pattern.match(phone):
errors['phone'] = '手机号格式不正确(11位数字,以1开头)'
model = data.get('device_model', '').strip()
if not model:
errors['device_model'] = '电脑型号不能为空'
fault = data.get('fault_description', '').strip()
if not fault:
errors['fault_description'] = '故障描述不能为空'
elif len(fault) < 5:
errors['fault_description'] = '故障描述至少需要5个字符'
return errors
# ======================================================
# Flask 路由定义
# ======================================================
@app.route('/')
def index():
"""显示表单页面"""
return render_template('kehu.html')
@app.route('/edit')
def edit_page():
"""客户管理页面"""
return render_template('kehu-edit.html')
@app.route('/submit', methods=['POST'])
def submit():
"""处理表单提交"""
print("收到提交请求")
print("表单数据:", request.form)
form_data = {
'customer_name': request.form.get('customer_name', '').strip(),
'phone': request.form.get('phone', '').strip(),
'device_model': request.form.get('device_model', '').strip(),
'fault_description': request.form.get('fault_description', '').strip(),
'sn_code': request.form.get('sn_code') or None,
'customer_email': request.form.get('customer_email') or None,
'customer_qq': request.form.get('customer_qq') or None,
'customer_wechat': request.form.get('customer_wechat') or None,
}
errors = validate_form_data(form_data)
if errors:
return jsonify({'success': False, 'errors': errors, 'message': '验证失败'}), 400
db = KehuDB()
success, message, insert_id = db.insert_customer(form_data)
if success:
return jsonify({'success': True, 'message': '维修申请提交成功!', 'id': insert_id})
else:
return jsonify({'success': False, 'message': message}), 500
@app.route('/records', methods=['GET'])
def get_records():
"""获取分页记录(支持搜索)"""
page = int(request.args.get('page', 1))
size = int(request.args.get('size', 10))
name = request.args.get('name', '')
phone = request.args.get('phone', '')
status = request.args.get('status', '')
db = KehuDB()
records, total = db.get_records_paginated(page, size, name, phone, status)
# 只在这里格式化时间,不影响查询
for record in records:
if record.get('submit_time'):
if hasattr(record['submit_time'], 'strftime'):
record['submit_time'] = record['submit_time'].strftime('%Y-%m-%d %H:%M:%S')
if record.get('update_time'):
if hasattr(record['update_time'], 'strftime'):
record['update_time'] = record['update_time'].strftime('%Y-%m-%d %H:%M:%S')
return jsonify({
'success': True,
'data': records,
'total': total,
'page': page,
'size': size,
'totalPages': (total + size - 1) // size
})
@app.route('/record/<int:record_id>', methods=['GET'])
def get_record(record_id):
db = KehuDB()
record = db.get_record_by_id(record_id)
if record:
# 格式化时间
if record.get('submit_time'):
if hasattr(record['submit_time'], 'strftime'):
record['submit_time'] = record['submit_time'].strftime('%Y-%m-%d %H:%M:%S')
if record.get('update_time'):
if hasattr(record['update_time'], 'strftime'):
record['update_time'] = record['update_time'].strftime('%Y-%m-%d %H:%M:%S')
return jsonify({'success': True, 'data': record})
return jsonify({'success': False, 'message': '记录不存在'}), 404
@app.route('/record', methods=['POST'])
def create_record():
"""新增客户"""
data = request.get_json()
# 验证必填字段
if not data.get('customer_name') or not data.get('phone') or not data.get('device_model') or not data.get(
'fault_description'):
return jsonify({'success': False, 'message': '请填写所有必填项'}), 400
if not re.match(r'^1[0-9]{10}$', data.get('phone', '')):
return jsonify({'success': False, 'message': '手机号格式不正确'}), 400
db = KehuDB()
success, message, insert_id = db.create_customer(data)
if success:
return jsonify({'success': True, 'message': message, 'id': insert_id})
return jsonify({'success': False, 'message': message}), 500
@app.route('/record/<int:record_id>', methods=['PUT'])
def update_record(record_id):
"""更新客户(完整版)"""
data = request.get_json()
# 如果只是更新状态(兼容旧接口)
if 'status' in data and len(data) == 1:
status = data.get('status')
if not status or status not in [1, 2, 3, 4, 5]:
return jsonify({'success': False, 'message': '无效的状态值'}), 400
db = KehuDB()
success = db.update_status(record_id, status)
if success:
return jsonify({'success': True, 'message': '状态更新成功'})
return jsonify({'success': False, 'message': '更新失败'}), 500
# 完整更新
db = KehuDB()
success, message = db.update_customer(record_id, data)
if success:
return jsonify({'success': True, 'message': message})
return jsonify({'success': False, 'message': message}), 500
@app.route('/record/<int:record_id>', methods=['DELETE'])
def delete_record(record_id):
"""删除记录"""
db = KehuDB()
success, message = db.delete_customer(record_id)
if success:
return jsonify({'success': True, 'message': message})
return jsonify({'success': False, 'message': message}), 500
@app.route('/search', methods=['GET'])
def search_by_phone():
phone = request.args.get('phone', '')
if not phone:
return jsonify({'success': False, 'message': '请提供手机号'}), 400
db = KehuDB()
records = db.get_records_by_phone(phone)
return jsonify({'success': True, 'data': records})
@app.route('/test-db')
def test_db():
"""测试数据库连接"""
db = KehuDB()
if db.connect():
db.close()
return jsonify({'success': True, 'message': '数据库连接成功'})
return jsonify({'success': False, 'message': '数据库连接失败'}), 500
if __name__ == '__main__':
print("=" * 50)
print("启动服务器...")
print("访问地址: http://localhost:5000")
print("客户登记: http://localhost:5000")
print("客户管理: http://localhost:5000/edit")
print("测试数据库: http://localhost:5000/test-db")
print("=" * 50)
app.run(debug=True, host='0.0.0.0', port=5000)
ok了

更多推荐



所有评论(0)