Cordova 开发鸿蒙PC应用快递查询应用实现技术博客

目录

  1. 项目概述
  2. 技术选型
  3. 功能需求分析
  4. 实现步骤
  5. 核心代码解析
  6. API 集成详解
  7. 用户体验优化
  8. 常见问题与解决方案
  9. 最佳实践
  10. 总结

项目概述

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

效果

image-20251121174523083

核心功能

  • 多快递公司支持:支持中通、申通、顺丰、极兔、百世、圆通等主流快递公司
  • 自动识别快递公司:系统能够自动识别快递单号对应的快递公司
  • 手动选择快递公司:支持手动选择快递公司,提高查询准确性
  • 手机号查询:支持手机号后四位查询(顺丰/中通必填)
  • 物流轨迹展示:时间线形式展示完整的物流轨迹信息
  • 状态标签显示:清晰显示快递状态(运输中、已签收、已揽收等)
  • 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: 顺丰/中通查询失败

原因:缺少手机号后四位

解决方案

  1. 在表单中提示用户输入手机号后四位
  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: 自动识别快递公司不准确

原因:某些快递单号格式相似

解决方案

  1. 提供手动选择快递公司的选项
  2. 在查询失败时提示用户手动选择

问题 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 应用中实现快递查询功能,包括:

  1. 完整的实现方案:从页面设计到 API 集成
  2. 双 API 集成:快递公司列表和快递查询
  3. 用户体验优化:表单验证、加载提示、错误处理
  4. 时间线可视化:美观的物流轨迹展示
  5. Token 管理:自动保存和获取引导
  6. 响应式设计:完美适配各种设备
  7. 最佳实践:代码组织、错误处理、性能优化

关键技术点

  • 双 API 调用:快递公司列表 + 快递查询
  • 时间线设计:CSS 实现物流轨迹可视化
  • 状态映射:友好的状态显示
  • 自动加载:页面加载时自动获取快递公司列表
  • LocalStorage:客户端数据持久化
  • 响应式 CSS:移动端适配

项目亮点

  • 📦 多快递公司支持:支持主流快递公司查询
  • 🤖 智能识别:自动识别快递公司
  • 📱 时间线展示:直观的物流轨迹可视化
  • 🏷️ 状态标签:清晰的状态显示
  • 💾 智能的 Token 管理:自动保存、一键获取
  • 📱 完美的移动端适配:响应式布局、触摸优化
  • 良好的性能:快速响应、流畅交互

应用场景

  • 个人用户:查询自己的快递物流信息
  • 电商平台:集成到订单详情页
  • 物流管理:批量查询和管理快递
  • 企业应用:内部物流跟踪系统

通过本文的指导,您可以快速实现一个功能完整、用户体验良好的快递查询应用。希望本文对您的开发工作有所帮助!


作者: 坚果派开发团队
最后更新: 2025年
版本: 1.0

参考资源

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐