我们将主要使用 Python 作为后端语言,因为它在处理数据和API请求方面非常强大和便捷,前端界面则使用 HTML + CSS + JavaScript,这是最基础和通用的组合。
纯前端静态页面(最简单)
这个方案不涉及后端服务器,直接在浏览器中运行,它使用一个模拟的、硬编码的物流数据,非常适合快速原型演示或学习前端基础。
文件结构
/logistics-query
├── index.html
├── style.css
└── script.js
index.html (页面结构)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">物流查询</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>物流信息查询</h1>
<div class="input-area">
<input type="text" id="tracking-number" placeholder="请输入快递单号">
<button id="query-btn">查询</button>
</div>
<div id="result-area" class="hidden">
<h2>物流轨迹</h2>
<div id="logistics-info"></div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
style.css (页面样式)
body {
font-family: 'Arial', sans-serif;
background-color: #f4f7f6;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.container {
background-color: #ffffff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 600px;
text-align: center;
}
h1 {
color: #333;
}
.input-area {
margin: 20px 0;
}
#tracking-number {
width: 70%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
#query-btn {
width: 25%;
padding: 12px;
border: none;
background-color: #007bff;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
#query-btn:hover {
background-color: #0056b3;
}
#result-area {
text-align: left;
margin-top: 20px;
}
#result-area.hidden {
display: none;
}
.logistics-item {
border-left: 3px solid #007bff;
padding-left: 15px;
margin-bottom: 15px;
position: relative;
}
.logistics-item::before {
content: '';
position: absolute;
left: -8px;
top: 5px;
width: 13px;
height: 13px;
border-radius: 50%;
background-color: #007bff;
}
.time {
font-size: 0.9em;
color: #666;
margin-bottom: 5px;
}
.status {
font-weight: bold;
color: #333;
}
script.js (页面交互逻辑)
document.addEventListener('DOMContentLoaded', () => {
const queryBtn = document.getElementById('query-btn');
const trackingNumberInput = document.getElementById('tracking-number');
const resultArea = document.getElementById('result-area');
const logisticsInfo = document.getElementById('logistics-info');
// 模拟的物流数据
const mockData = {
'SF1234567890': [
{ time: '2025-10-27 10:30:00', status: '快件已签收,签收人:本人' },
{ time: '2025-10-27 09:15:00', status: '【深圳市南山区】快件已由派件员[张三]本人签收' },
{ time: '2025-10-27 08:00:00', status: '【深圳市南山区】快件正在派送途中,请保持电话畅通' },
{ time: '2025-10-26 22:30:00', status: '【深圳市南山区】快件已到达【南山营业点】,派件员[张三]电话:13800138000' },
{ time: '2025-10-26 20:15:00', status: '【广州市】快件已离开广州转运中心' },
{ time: '2025-10-26 15:00:00', status: '【广州市】快件已到达广州转运中心' },
],
'YT9876543210': [
{ time: '2025-10-27 11:00:00', status: '快件已由菜鸟驿站代收' },
{ time: '2025-10-27 10:00:00', status: '【上海市浦东新区】快递员[李四]正在派送中' },
{ time: '2025-10-26 18:00:00', status: '【上海市浦东新区】快件已到达【浦东新区营业点】' },
]
};
queryBtn.addEventListener('click', () => {
const trackingNumber = trackingNumberInput.value.trim();
if (!trackingNumber) {
alert('请输入快递单号!');
return;
}
// 清空之前的结果
logisticsInfo.innerHTML = '';
// 模拟网络延迟
resultArea.classList.add('hidden');
setTimeout(() => {
const data = mockData[trackingNumber];
if (data) {
// 将数据倒序显示,最新的在上面
data.reverse().forEach(item => {
const div = document.createElement('div');
div.className = 'logistics-item';
div.innerHTML = `
<div class="time">${item.time}</div>
<div class="status">${item.status}</div>
`;
logisticsInfo.appendChild(div);
});
resultArea.classList.remove('hidden');
} else {
logisticsInfo.innerHTML = '<p>未找到相关物流信息,请检查单号是否正确。</p>';
resultArea.classList.remove('hidden');
}
}, 500); // 模拟0.5秒的加载时间
});
// 回车键也可以触发查询
trackingNumberInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
queryBtn.click();
}
});
});
后端 API + 前端调用(真实项目常用)
这个方案更接近真实世界的应用,前端通过JavaScript向后端服务器发送请求,后端再去调用真实的物流查询API(如快递100、聚合数据等),然后将结果返回给前端。
后端实现 (使用 Python + Flask)
你需要安装 Flask 库:
pip install Flask
创建一个简单的后端服务 app.py。
注意: 你需要先在快递100等平台注册并获取一个 API Key。
# app.py
from flask import Flask, request, jsonify
import requests
import json
app = Flask(__name__)
# !!! 重要:请替换成你自己的快递100 API Key 和 Customer
KUAIDI100_API_KEY = '你的快递100APIKey'
KUAIDI100_CUSTOMER = '你的快递100客户ID'
# 模拟的物流数据(当API调用失败时使用)
MOCK_DATA = {
'SF1234567890': [
{'time': '2025-10-27 10:30:00', 'status': '快件已签收,签收人:本人'},
{'time': '2025-10-27 09:15:00', 'status': '【深圳市南山区】快件已由派件员[张三]本人签收'},
{'time': '2025-10-27 08:00:00', 'status': '【深圳市南山区】快件正在派送途中,请保持电话畅通'},
{'time': '2025-10-26 22:30:00', 'status': '【深圳市南山区】快件已到达【南山营业点】,派件员[张三]电话:13800138000'},
{'time': '2025-10-26 20:15:00', 'status': '【广州市】快件已离开广州转运中心'},
{'time': '2025-10-26 15:00:00', 'status': '【广州市】快件已到达广州转运中心'},
],
'YT9876543210': [
{'time': '2025-10-27 11:00:00', 'status': '快件已由菜鸟驿站代收'},
{'time': '2025-10-27 10:00:00', 'status': '【上海市浦东新区】快递员[李四]正在派送中'},
{'time': '2025-10-26 18:00:00', 'status': '【上海市浦东新区】快件已到达【浦东新区营业点】'},
]
}
@app.route('/api/query', methods=['GET'])
def query_logistics():
tracking_number = request.args.get('trackingNumber')
company_code = request.args.get('company')
if not tracking_number or not company_code:
return jsonify({'error': '缺少参数: trackingNumber 或 company'}), 400
# --- 调用真实API的代码 ---
# try:
# url = f'http://poll.kuaidi100.com/poll/query.do'
# params = {
# 'customer': KUAIDI100_CUSTOMER,
# 'param': json.dumps({'com': company_code, 'num': tracking_number}, ensure_ascii=False),
# }
# headers = {
# 'Authorization': 'APICode ' + KUAIDI100_API_KEY
# }
# response = requests.post(url, data=params, headers=headers)
# result = response.json()
#
# if result.get('returnCode') == '200':
# # 提取物流轨迹数据
# tracks = result.get('data', {}).get('trails', [])
# formatted_tracks = []
# for track in tracks:
# formatted_tracks.append({
# 'time': track['time'],
# 'status': track['content']
# })
# return jsonify({'success': True, 'data': formatted_tracks})
# else:
# return jsonify({'success': False, 'message': result.get('message', '查询失败')}), 404
#
# except Exception as e:
# print(f"API调用失败: {e}")
# return jsonify({'success': False, 'message': '服务器内部错误'}), 500
# --- 模拟API返回 ---
# 在真实环境中,请注释掉上面的代码块,并取消注释下面的代码块
data = MOCK_DATA.get(tracking_number)
if data:
return jsonify({'success': True, 'data': data})
else:
return jsonify({'success': False, 'message': '未找到物流信息'}), 404
if __name__ == '__main__':
app.run(debug=True)
前端修改 (script.js)
前端代码需要修改,不再使用本地数据,而是通过 fetch API 调用我们刚刚创建的后端接口。
// script.js (修改版)
document.addEventListener('DOMContentLoaded', () => {
const queryBtn = document.getElementById('query-btn');
const trackingNumberInput = document.getElementById('tracking-number');
const resultArea = document.getElementById('result-area');
const logisticsInfo = document.getElementById('logistics-info');
queryBtn.addEventListener('click', async () => {
const trackingNumber = trackingNumberInput.value.trim();
if (!trackingNumber) {
alert('请输入快递单号!');
return;
}
// 清空之前的结果
logisticsInfo.innerHTML = '';
resultArea.classList.add('hidden');
// 在实际应用中,你可能需要一个下拉菜单来选择快递公司
// 这里我们用一个硬编码的值作为示例
const companyCode = 'SF'; // SF-顺丰, YT-圆通
try {
// 显示一个加载中的提示
logisticsInfo.innerHTML = '<p>查询中,请稍候...</p>';
resultArea.classList.remove('hidden');
const response = await fetch(`/api/query?trackingNumber=${trackingNumber}&company=${companyCode}`);
const result = await response.json();
if (result.success) {
// 将数据倒序显示,最新的在上面
result.data.reverse().forEach(item => {
const div = document.createElement('div');
div.className = 'logistics-item';
div.innerHTML = `
<div class="time">${item.time}</div>
<div class="status">${item.status}</div>
`;
logisticsInfo.appendChild(div);
});
} else {
logisticsInfo.innerHTML = `<p>${result.message}</p>`;
}
} catch (error) {
console.error('请求失败:', error);
logisticsInfo.innerHTML = '<p>服务器连接失败,请稍后再试。</p>';
}
});
trackingNumberInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
queryBtn.click();
}
});
});
如何运行这个方案?
- 确保你已经安装了 Python 和 Flask。
- 将
app.py和index.html,style.css,script.js放在同一个项目文件夹下。 - 打开终端,进入该文件夹,运行
python app.py。 - 打开浏览器,访问
http://127.0.0.1:5000。
更完整的前端实现(增加快递公司选择)
这是一个对方案二的增强版,在前端增加了一个下拉选择框,让用户可以选择快递公司。
修改 index.html
<!-- 在 input-area div 中增加一个下拉选择框 -->
<div class="input-area">
<select id="company-select">
<option value="">请选择快递公司</option>
<option value="SF">顺丰速运</option>
<option value="YT">圆通速递</option>
<option value="STO">申通快递</option>
<option value="YTO">中通快递</option>
<option value="JD">京东物流</option>
</select>
<input type="text" id="tracking-number" placeholder="请输入快递单号">
<button id="query-btn">查询</button>
</div>
修改 script.js
// script.js (完整版)
document.addEventListener('DOMContentLoaded', () => {
const queryBtn = document.getElementById('query-btn');
const trackingNumberInput = document.getElementById('tracking-number');
const companySelect = document.getElementById('company-select');
const resultArea = document.getElementById('result-area');
const logisticsInfo = document.getElementById('logistics-info');
queryBtn.addEventListener('click', async () => {
const trackingNumber = trackingNumberInput.value.trim();
const companyCode = companySelect.value;
if (!trackingNumber) {
alert('请输入快递单号!');
return;
}
if (!companyCode) {
alert('请选择快递公司!');
return;
}
logisticsInfo.innerHTML = '<p>查询中,请稍候...</p>';
resultArea.classList.remove('hidden');
try {
const response = await fetch(`/api/query?trackingNumber=${trackingNumber}&company=${companyCode}`);
const result = await response.json();
if (result.success) {
logisticsInfo.innerHTML = ''; // 清空加载提示
result.data.reverse().forEach(item => {
const div = document.createElement('div');
div.className = 'logistics-item';
div.innerHTML = `
<div class="time">${item.time}</div>
<div class="status">${item.status}</div>
`;
logisticsInfo.appendChild(div);
});
} else {
logisticsInfo.innerHTML = `<p>${result.message}</p>`;
}
} catch (error) {
console.error('请求失败:', error);
logisticsInfo.innerHTML = '<p>服务器连接失败,请稍后再试。</p>';
}
});
// 回车键触发查询
trackingNumberInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
queryBtn.click();
}
});
});
总结与建议
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 方案一 (纯前端) | 实现简单,无需服务器,适合快速原型。 | 数据是硬编码的,无法查询真实物流。 | 学习、演示、个人静态页面。 |
| 方案二 (后端API) | 结构清晰,前后端分离,可扩展性强,能接入真实数据。 | 需要搭建后端服务器,有服务器成本。 | 小型Web应用、企业内部工具。 |
| 方案三 (完整前端) | 用户体验好,功能更完善。 | 在方案二基础上增加了前端复杂度。 | 对用户体验要求较高的公开Web应用。 |
给你的建议:
- 如果你是初学者,从 方案一 开始,理解HTML、CSS、JS是如何协同工作的。
- 如果你想做一个能用的真实项目,直接上手 方案三,并配合 方案二 的后端代码,这是目前最主流和推荐的实践方式。
- 关于物流API:国内的快递100、聚合数据等平台都提供免费或付费的API服务,它们能自动识别快递公司,你只需要提供单号即可,这样前端就可以省去选择快递公司的步骤,体验会更好。
