Cordova 开发鸿蒙PC应用快递查询应用实现技术博客
目录
项目概述
快递查询应用是一个基于 Cordova 框架开发的移动应用,通过调用第三方 API 实现快递物流轨迹查询功能。用户只需输入快递单号,即可查询快递的实时物流状态和详细轨迹信息。应用支持自动识别快递公司,也支持手动选择,并针对顺丰和中通快递提供了手机号后四位查询功能。
效果

核心功能
- ✅ 多快递公司支持:支持中通、申通、顺丰、极兔、百世、圆通等主流快递公司
- ✅ 自动识别快递公司:系统能够自动识别快递单号对应的快递公司
- ✅ 手动选择快递公司:支持手动选择快递公司,提高查询准确性
- ✅ 手机号查询:支持手机号后四位查询(顺丰/中通必填)
- ✅ 物流轨迹展示:时间线形式展示完整的物流轨迹信息
- ✅ 状态标签显示:清晰显示快递状态(运输中、已签收、已揽收等)
- ✅ Token 管理:自动保存和加载 API Token
- ✅ 响应式设计:完美适配移动设备和桌面浏览器
技术选型
前端技术栈
- HTML5:页面结构
- CSS3:样式设计和响应式布局
- JavaScript (ES6+):业务逻辑和 API 调用
- Cordova:跨平台移动应用框架
API 服务
- API 提供商:AlAPI(alapi.cn)
- 快递查询接口:
https://v3.alapi.cn/api/kd - 快递公司列表接口:
https://v3.alapi.cn/api/kd/com - 请求方式:POST
- 数据格式:URL-encoded form data
功能需求分析
1. 用户输入需求
- API Token(必填)
- 快递单号(必填)
- 快递公司(可选,默认自动识别)
- 手机号后四位(可选,顺丰/中通必填)
2. 功能需求
- 快递公司列表自动加载
- 快递查询和错误处理
- 物流轨迹时间线展示
- Token 本地存储
- 加载状态提示
- 错误信息提示
3. 用户体验需求
- 响应式设计
- 流畅的交互动画
- 清晰的错误提示
- Token 获取引导
- 时间线可视化展示
实现步骤
步骤 1: 创建页面结构
创建 express.html 文件,包含:
- 导航栏
- 页面标题和说明
- 表单区域(Token、快递单号、快递公司、手机号)
- 结果展示区域(物流轨迹时间线)
- Token 获取弹窗
步骤 2: 设计 UI 样式
使用 CSS3 实现:
- 响应式布局
- 渐变背景和阴影效果
- 表单样式和焦点效果
- 时间线样式设计
- 状态标签样式
- 弹窗样式
- 移动端适配
步骤 3: 实现 JavaScript 逻辑
创建 express.js 文件,实现:
- 快递公司列表加载
- 表单提交处理
- API 调用
- 数据验证
- 结果展示(时间线)
- Token 存储
步骤 4: 配置安全策略
更新 Content Security Policy (CSP),允许:
- 访问 API 域名
- 加载 iframe 内容
- 执行必要的脚本
核心代码解析
1. HTML 结构
表单部分
<form id="express-form">
<div class="form-group">
<label for="token" class="label-with-help">
<span>API Token *</span>
<button type="button" class="help-btn" onclick="openTokenModal()">获取 Token</button>
</label>
<div class="token-input-wrapper">
<input type="text" id="token" name="token" placeholder="请输入API Token" required>
</div>
</div>
<div class="form-group">
<label for="number">快递单号 *</label>
<input type="text" id="number" name="number" placeholder="请输入快递单号" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="com">快递公司(可选)</label>
<select id="com" name="com">
<option value="">自动识别</option>
</select>
</div>
<div class="form-group">
<label for="phone">手机号后四位(可选)</label>
<input type="text" id="phone" name="phone" placeholder="顺丰/中通必填" maxlength="4" pattern="[0-9]{4}">
</div>
</div>
<button type="submit" class="submit-btn" id="submit-btn">查询</button>
</form>
结果展示区域
<div class="result-container" id="result-container">
<div class="result-title">查询结果</div>
<div class="express-info" id="express-info"></div>
</div>
2. CSS 样式设计
时间线样式
.tracking-list {
list-style: none;
padding: 0;
margin: 0;
position: relative;
}
.tracking-list::before {
content: '';
position: absolute;
left: 15px;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(to bottom, #3498db, #e0e0e0);
}
.tracking-item {
position: relative;
padding-left: 45px;
padding-bottom: 25px;
border-left: 2px solid transparent;
}
.tracking-item::before {
content: '';
position: absolute;
left: 8px;
top: 5px;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #3498db;
border: 3px solid white;
box-shadow: 0 0 0 2px #3498db;
}
.tracking-item:first-child::before {
background-color: #27ae60;
box-shadow: 0 0 0 2px #27ae60;
}
状态标签样式
.status-badge {
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
}
.status-badge.transport {
background-color: #e3f2fd;
color: #1976d2;
}
.status-badge.signed {
background-color: #e8f5e9;
color: #388e3c;
}
.status-badge.collect {
background-color: #fff3e0;
color: #f57c00;
}
3. JavaScript 核心逻辑
快递公司列表加载
// 快递公司列表
let expressCompanies = [];
// 加载快递公司列表
function loadExpressCompanies() {
const token = localStorage.getItem('express_api_token');
if (!token) {
console.log('未找到Token,跳过加载快递公司列表');
return;
}
const params = new URLSearchParams();
params.append('token', token);
fetch('https://v3.alapi.cn/api/kd/com', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString()
})
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败: ' + response.status);
}
return response.json();
})
.then(data => {
console.log('快递公司列表响应:', data);
if (data.success && data.code === 200 && Array.isArray(data.data)) {
expressCompanies = data.data;
updateCompanySelect(data.data);
} else {
console.error('获取快递公司列表失败:', data.message);
}
})
.catch(error => {
console.error('获取快递公司列表错误:', error);
});
}
更新快递公司下拉框
function updateCompanySelect(companies) {
const select = document.getElementById('com');
// 保留"自动识别"选项
const autoOption = select.querySelector('option[value=""]');
select.innerHTML = '';
if (autoOption) {
select.appendChild(autoOption);
}
companies.forEach(company => {
const option = document.createElement('option');
option.value = company.code;
option.textContent = company.name;
select.appendChild(option);
});
}
快递查询函数
function queryExpress() {
const submitBtn = document.getElementById('submit-btn');
const loading = document.getElementById('loading');
const errorMessage = document.getElementById('error-message');
const resultContainer = document.getElementById('result-container');
// 获取表单数据
const formData = {
token: document.getElementById('token').value.trim(),
number: document.getElementById('number').value.trim(),
com: document.getElementById('com').value,
phone: document.getElementById('phone').value.trim()
};
// 验证快递单号
if (!formData.number) {
showError('请输入快递单号');
return;
}
// 验证token
if (!formData.token) {
showError('请输入API Token');
return;
}
// 显示加载状态
submitBtn.disabled = true;
submitBtn.textContent = '查询中...';
loading.classList.add('show');
errorMessage.classList.remove('show');
resultContainer.classList.remove('show');
// 构建请求参数
const params = new URLSearchParams();
params.append('token', formData.token);
params.append('number', formData.number);
if (formData.com) {
params.append('com', formData.com);
}
if (formData.phone) {
params.append('phone', formData.phone);
}
// 发送POST请求
fetch('https://v3.alapi.cn/api/kd', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString()
})
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败: ' + response.status);
}
return response.json();
})
.then(data => {
console.log('API响应:', data);
if (data.success && data.code === 200) {
displayExpressInfo(data.data);
// 如果成功查询,更新快递公司列表
if (!expressCompanies.length) {
loadExpressCompanies();
}
} else {
showError(data.message || '查询失败,请检查参数');
}
})
.catch(error => {
console.error('请求错误:', error);
showError('请求失败: ' + error.message + '。请检查网络连接或API Token是否正确');
})
.finally(() => {
// 恢复按钮状态
submitBtn.disabled = false;
submitBtn.textContent = '查询';
loading.classList.remove('show');
});
}
状态映射
// 状态映射
const statusMap = {
'TRANSPORT': { text: '运输中', class: 'transport' },
'SIGNED': { text: '已签收', class: 'signed' },
'COLLECT': { text: '已揽收', class: 'collect' }
};
结果展示函数
function displayExpressInfo(data) {
const resultContainer = document.getElementById('result-container');
const expressInfo = document.getElementById('express-info');
let html = '';
// 快递基本信息
html += '<div class="info-header">';
html += '<div class="info-header-left">';
html += `<h3>${data.com_text || '未知快递公司'}</h3>`;
html += `<p>快递单号:${data.nu || '未知'}</p>`;
html += '</div>';
// 状态标签
const status = statusMap[data.status] || { text: data.status_desc || '未知', class: 'transport' };
html += `<span class="status-badge ${status.class}">${status.text}</span>`;
html += '</div>';
// 物流轨迹列表
if (data.info && Array.isArray(data.info) && data.info.length > 0) {
html += '<ul class="tracking-list">';
data.info.forEach((item, index) => {
const itemStatus = statusMap[item.status] || { text: item.status_desc || '', class: 'transport' };
html += '<li class="tracking-item">';
html += '<div class="tracking-content">';
html += `<div class="tracking-time">${item.time || ''}</div>`;
html += `<div class="tracking-text">${item.content || ''}</div>`;
if (item.status_desc) {
html += `<div style="margin-top: 5px; font-size: 12px; color: #7f8c8d;">状态:${item.status_desc}</div>`;
}
html += '</div>';
html += '</li>';
});
html += '</ul>';
} else {
html += '<div style="text-align: center; padding: 20px; color: #7f8c8d;">暂无物流轨迹信息</div>';
}
expressInfo.innerHTML = html;
// 显示结果容器
resultContainer.classList.add('show');
// 滚动到结果区域
resultContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
API 集成详解
1. 快递公司列表 API
请求参数
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| token | string | 是 | 接口调用token,需要在token管理中创建 | qlVquQZPYSeaCi6u |
响应格式
{
"request_id": "727661733887553536",
"success": true,
"message": "success",
"code": 200,
"data": [
{"name": "申通快递", "code": "sto"},
{"name": "圆通快递", "code": "yto"},
{"name": "顺丰快递", "code": "sf"},
{"name": "中通快递", "code": "zto"},
{"name": "百世快运", "code": "best"},
{"name": "极兔快递", "code": "jt"}
],
"time": 1734448076,
"usage": 0
}
2. 快递查询 API
请求参数
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| token | string | 是 | 接口调用token,需要在token管理中创建 | qlVquQZPYSeaCi6u |
| number | string | 是 | 要查询的快递编号 | 18118763460 |
| com | string | 否 | 快递公司编码,默认自动识别,不用填写 | sto |
| phone | string | 否 | 寄/收件人手机号后四位,顺丰快递和中通快递必填 | 1234 |
响应格式
{
"request_id": "727660081369522176",
"success": true,
"message": "success",
"code": 200,
"data": {
"nu": "18118763460",
"com": "best",
"com_text": "百世快运",
"state": 3,
"status": "TRANSPORT",
"status_desc": "运输中",
"info": [
{
"time": "2019-11-18 15:53:02",
"content": "已签收(4/4)。有问题请联系派件员杨娇19136004279,投诉电话18040471611。",
"status": "TRANSPORT",
"status_desc": "运输中"
}
]
},
"time": 1734447686,
"usage": 0
}
响应参数说明
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| nu | string | 是 | 快递编号 |
| com | string | 否 | 快递公司编码 |
| com_text | string | 否 | 快递公司名称 |
| state | string | 否 | 签收状态,3已签收 |
| status | string | 否 | 最后一条物流状态 |
| status_desc | string | 否 | 物流状态描述 |
| info | array | 否 | 物流轨迹列表 |
| info[].time | string | 否 | 轨迹更新时间 |
| info[].content | string | 否 | 轨迹更新内容 |
| info[].status | string | 否 | 轨迹状态 |
| info[].status_desc | string | 否 | 状态描述 |
3. 请求实现
// 构建请求参数
const params = new URLSearchParams();
params.append('token', formData.token);
params.append('number', formData.number);
if (formData.com) {
params.append('com', formData.com);
}
if (formData.phone) {
params.append('phone', formData.phone);
}
// 发送POST请求
fetch('https://v3.alapi.cn/api/kd', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString()
})
用户体验优化
1. 自动加载快递公司列表
// 页面加载时自动获取快递公司列表
document.addEventListener('deviceready', function() {
console.log('设备就绪,快递查询功能可用');
loadExpressCompanies();
}, false);
// Token 更新后重新加载
document.getElementById('token').addEventListener('blur', function() {
const token = this.value.trim();
if (token) {
localStorage.setItem('express_api_token', token);
loadExpressCompanies();
}
});
2. 时间线可视化展示
使用 CSS 实现时间线效果:
.tracking-list::before {
content: '';
position: absolute;
left: 15px;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(to bottom, #3498db, #e0e0e0);
}
.tracking-item::before {
content: '';
position: absolute;
left: 8px;
top: 5px;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #3498db;
border: 3px solid white;
box-shadow: 0 0 0 2px #3498db;
}
.tracking-item:first-child::before {
background-color: #27ae60; /* 最新状态使用绿色 */
box-shadow: 0 0 0 2px #27ae60;
}
3. 状态标签颜色区分
const statusMap = {
'TRANSPORT': { text: '运输中', class: 'transport' }, // 蓝色
'SIGNED': { text: '已签收', class: 'signed' }, // 绿色
'COLLECT': { text: '已揽收', class: 'collect' } // 橙色
};
4. 手机号输入验证
<input type="text" id="phone" name="phone"
placeholder="顺丰/中通必填"
maxlength="4"
pattern="[0-9]{4}">
5. 错误处理和提示
function showError(message) {
const errorMessage = document.getElementById('error-message');
errorMessage.textContent = message;
errorMessage.classList.add('show');
}
常见问题与解决方案
问题 1: 快递公司列表加载失败
原因:Token 未设置或无效
解决方案:
function loadExpressCompanies() {
const token = localStorage.getItem('express_api_token');
if (!token) {
console.log('未找到Token,跳过加载快递公司列表');
return; // 静默失败,不影响主功能
}
// ... 加载逻辑
}
问题 2: 顺丰/中通查询失败
原因:缺少手机号后四位
解决方案:
- 在表单中提示用户输入手机号后四位
- 在查询前验证是否为顺丰/中通,并检查手机号
// 可以添加验证逻辑
if ((formData.com === 'sf' || formData.com === 'zto') && !formData.phone) {
showError('顺丰/中通快递需要输入手机号后四位');
return;
}
问题 3: 时间线显示不完整
原因:CSS 样式问题或数据格式问题
解决方案:
.tracking-item {
position: relative;
padding-left: 45px;
padding-bottom: 25px;
}
.tracking-content {
background-color: #f8f9fa;
padding: 15px;
border-radius: 6px;
word-wrap: break-word; /* 长文本自动换行 */
}
问题 4: 自动识别快递公司不准确
原因:某些快递单号格式相似
解决方案:
- 提供手动选择快递公司的选项
- 在查询失败时提示用户手动选择
问题 5: 物流轨迹为空
原因:快递单号错误或快递公司不支持
解决方案:
if (data.info && Array.isArray(data.info) && data.info.length > 0) {
// 显示轨迹
} else {
html += '<div style="text-align: center; padding: 20px; color: #7f8c8d;">暂无物流轨迹信息</div>';
}
最佳实践
1. 代码组织
- 分离关注点:HTML 负责结构,CSS 负责样式,JavaScript 负责逻辑
- 模块化设计:将功能拆分为独立的函数
- 命名规范:使用有意义的变量和函数名
2. 错误处理
fetch('https://v3.alapi.cn/api/kd', {
method: 'POST',
// ...
})
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败: ' + response.status);
}
return response.json();
})
.then(data => {
if (data.success && data.code === 200) {
displayExpressInfo(data.data);
} else {
showError(data.message || '查询失败,请检查参数');
}
})
.catch(error => {
console.error('请求错误:', error);
showError('请求失败: ' + error.message);
});
3. 性能优化
// 缓存快递公司列表
let expressCompanies = [];
// 只在 Token 更新时重新加载
document.getElementById('token').addEventListener('blur', function() {
const token = this.value.trim();
if (token && token !== localStorage.getItem('express_api_token')) {
localStorage.setItem('express_api_token', token);
loadExpressCompanies();
}
});
4. 用户体验
- 即时反馈:按钮状态、加载提示、错误信息
- 数据持久化:Token 自动保存
- 无障碍访问:合理的标签和提示文字
- 响应式设计:适配各种屏幕尺寸
5. 安全性
- 输入验证:前端和后端双重验证
- CSP 配置:限制资源加载来源
- Token 安全:不在代码中硬编码 Token
- HTTPS:生产环境使用 HTTPS
- XSS 防护:使用
textContent而非innerHTML(注意:本示例使用 innerHTML 是因为需要动态创建 HTML 结构,实际应用中应使用模板引擎或 DOM API)
项目结构
www/
├── express.html # 快递查询页面
├── js/
│ └── express.js # 快递查询功能脚本
├── index.html # 首页
├── about.html # 关于我们
├── poem.html # 藏头诗页面
├── translate.html # 翻译页面
└── css/
└── index.css # 样式文件
harmonyos/entry/src/main/resources/rawfile/www/
├── express.html # 快递查询页面(同步)
├── js/
│ └── express.js # 快递查询功能脚本(同步)
└── ...
部署说明
1. 开发环境
# 添加浏览器平台(用于测试)
hcordova platform add browser
# 运行浏览器预览
hcordova run browser
2. 生产环境
# 构建 HarmonyOS 应用
hcordova build harmonyos
# 构建 Android 应用
hcordova build android
# 构建 iOS 应用(仅 macOS)
hcordova build ios
3. 配置检查
- ✅ 检查
config.xml中的 CSP 配置 - ✅ 确认 API 域名在白名单中
- ✅ 测试 Token 获取功能
- ✅ 验证表单验证逻辑
- ✅ 测试各种快递公司的查询
测试建议
1. 功能测试
- 表单验证测试(快递单号、Token)
- API 调用测试(正常情况、错误情况)
- Token 存储测试(保存、加载)
- 快递公司列表加载测试
- 自动识别快递公司测试
- 手动选择快递公司测试
- 手机号后四位查询测试(顺丰/中通)
- 物流轨迹展示测试
2. 兼容性测试
- 不同浏览器测试(Chrome、Safari、Firefox)
- 移动设备测试(iOS、Android、HarmonyOS)
- 不同屏幕尺寸测试
- 不同快递公司测试
3. 性能测试
- API 响应时间
- 页面加载速度
- 内存使用情况
- 长列表渲染性能
扩展功能建议
1. 历史记录
// 保存查询历史
function saveHistory(expressData) {
const history = JSON.parse(localStorage.getItem('express_history') || '[]');
history.unshift({
number: expressData.nu,
company: expressData.com_text,
timestamp: Date.now()
});
// 只保留最近 20 条
if (history.length > 20) {
history.pop();
}
localStorage.setItem('express_history', JSON.stringify(history));
}
2. 收藏功能
// 收藏快递单号
function favoriteExpress(expressData) {
const favorites = JSON.parse(localStorage.getItem('express_favorites') || '[]');
favorites.push({
number: expressData.nu,
company: expressData.com_text,
phone: document.getElementById('phone').value
});
localStorage.setItem('express_favorites', JSON.stringify(favorites));
}
3. 推送通知
// 使用 Cordova 推送插件实现物流更新推送
function subscribeExpressUpdates(number) {
// 定期查询快递状态
setInterval(() => {
queryExpress();
// 如果状态更新,发送推送通知
}, 3600000); // 每小时查询一次
}
4. 分享功能
// 分享快递信息
function shareExpress(expressData) {
if (navigator.share) {
navigator.share({
title: `快递查询 - ${expressData.com_text}`,
text: `快递单号:${expressData.nu}\n状态:${expressData.status_desc}`
});
} else {
// 复制到剪贴板
navigator.clipboard.writeText(`快递单号:${expressData.nu}`);
alert('快递单号已复制到剪贴板');
}
}
5. 批量查询
// 支持批量查询多个快递单号
function batchQuery(numbers) {
const promises = numbers.map(number => {
return fetch('https://v3.alapi.cn/api/kd', {
method: 'POST',
body: new URLSearchParams({
token: token,
number: number
})
}).then(res => res.json());
});
Promise.all(promises).then(results => {
// 展示所有查询结果
});
}
总结
本文详细介绍了如何在 Cordova 应用中实现快递查询功能,包括:
- ✅ 完整的实现方案:从页面设计到 API 集成
- ✅ 双 API 集成:快递公司列表和快递查询
- ✅ 用户体验优化:表单验证、加载提示、错误处理
- ✅ 时间线可视化:美观的物流轨迹展示
- ✅ Token 管理:自动保存和获取引导
- ✅ 响应式设计:完美适配各种设备
- ✅ 最佳实践:代码组织、错误处理、性能优化
关键技术点
- 双 API 调用:快递公司列表 + 快递查询
- 时间线设计:CSS 实现物流轨迹可视化
- 状态映射:友好的状态显示
- 自动加载:页面加载时自动获取快递公司列表
- LocalStorage:客户端数据持久化
- 响应式 CSS:移动端适配
项目亮点
- 📦 多快递公司支持:支持主流快递公司查询
- 🤖 智能识别:自动识别快递公司
- 📱 时间线展示:直观的物流轨迹可视化
- 🏷️ 状态标签:清晰的状态显示
- 💾 智能的 Token 管理:自动保存、一键获取
- 📱 完美的移动端适配:响应式布局、触摸优化
- ⚡ 良好的性能:快速响应、流畅交互
应用场景
- 个人用户:查询自己的快递物流信息
- 电商平台:集成到订单详情页
- 物流管理:批量查询和管理快递
- 企业应用:内部物流跟踪系统
通过本文的指导,您可以快速实现一个功能完整、用户体验良好的快递查询应用。希望本文对您的开发工作有所帮助!
作者: 坚果派开发团队
最后更新: 2025年
版本: 1.0
参考资源:
1446

被折叠的 条评论
为什么被折叠?



