欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/

atomgit仓库地址:https://atomgit.com/feng8403000/shangmenweixiuyuyue

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、项目概述

1.1 项目背景

上门维修预约系统是一款面向普通家庭和维修服务从业者的专业预约平台。日常生活中,家庭设备难免出现各种故障需要维修,传统的找师傅方式存在信息不透明、价格不明确、服务质量参差不齐等问题。本系统通过将维修服务标准化、预约流程线上化,有效解决了这些痛点。

本项目基于鸿蒙PC Electron框架开发,采用纯前端技术实现,无需复杂的后端支持,即可提供完整的预约服务体验。系统涵盖了从服务浏览、在线预约、订单管理到个人中心等完整业务流程。

1.2 项目特点

特点 说明
专业服务 6大类维修服务,覆盖家庭常见需求
透明价格 明码标价,无隐性收费
便捷预约 在线预约,省时省力
订单追踪 实时查看订单状态
数据持久化 本地存储,关闭不丢失

1.3 功能模块

模块 功能
首页 Banner展示、服务分类、热门师傅、服务优势
预约维修 服务选择、表单填写、提交预约
我的订单 订单列表、状态筛选、订单操作
个人中心 用户信息、统计面板、菜单导航

二、技术架构设计

2.1 整体架构

┌─────────────────────────────────────────────────────────────┐
│                    前端应用层                              │
│         (HTML/CSS/JavaScript + 预约系统)                  │
├─────────────────────────────────────────────────────────────┤
│              Electron + Preload 层                        │
│              (IPC 通信、API 暴露)                        │
├─────────────────────────────────────────────────────────────┤
│               HarmonyOS 原生层                             │
│         (libadapter.so + ETS Adapters)                   │
└─────────────────────────────────────────────────────────────┘

2.2 模块划分

模块 职责 文件
视图层 HTML结构、页面组件 index.html
样式层 CSS样式、响应式布局 style.css
业务层 预约逻辑、订单管理 js/app.js
数据层 服务数据定义 js/app.js (内置)

2.3 数据结构设计

服务类型定义:

const serviceTypes = [
    { id: 1, name: '家电维修', icon: '🔌', price: 80, desc: '电视/空调/冰箱' },
    { id: 2, name: '水电安装', icon: '💧', price: 100, desc: '水管/龙头/阀门' },
    { id: 3, name: '管道疏通', icon: '🚿', price: 90, desc: '地漏/马桶/下水道' },
    { id: 4, name: '开锁换锁', icon: '🔐', price: 60, desc: '防盗门/卧室门' },
    { id: 5, name: '灯具安装', icon: '💡', price: 50, desc: '吸顶灯/吊灯/壁灯' },
    { id: 6, name: '墙面修补', icon: '🖼️', price: 70, desc: '刷漆/裂缝/孔洞' }
];

应用状态管理:

let appState = {
    currentTab: 'home',           // 当前标签页
    selectedService: null,        // 选中的服务
    orders: [],                   // 订单列表
    currentFilter: 'all',          // 当前筛选条件
    user: {                       // 用户信息
        name: '访客用户',
        phone: ''
    }
};

订单数据结构:

const order = {
    id: 'ORDm0abcXYZ',            // 订单ID
    serviceId: 1,                 // 服务类型ID
    serviceName: '家电维修',       // 服务名称
    serviceIcon: '🔌',            // 服务图标
    servicePrice: 80,             // 服务价格
    problemDesc: '空调不制冷',     // 问题描述
    bookDate: '2024-01-15',       // 预约日期
    bookTime: '09:00',            // 预约时间段
    userName: '张三',              // 用户姓名
    userPhone: '13800138000',     // 联系电话
    userAddress: '北京市朝阳区xxx', // 上门地址
    remark: '',                    // 备注
    status: 'pending',             // 订单状态
    createTime: '2024-01-14T10:30:00.000Z' // 创建时间
};

三、核心功能实现详解

3.1 应用初始化流程

应用启动时执行完整的初始化操作:

function initApp() {
    // 1. 从本地存储加载数据
    loadData();
    
    // 2. 渲染服务分类卡片
    renderCategories();
    
    // 3. 渲染服务选择器
    renderServiceSelector();
    
    // 4. 渲染订单列表
    renderOrders();
    
    // 5. 更新个人中心
    updateProfile();
    
    // 6. 设置事件监听
    setupEventListeners();
    
    // 7. 设置默认预约日期为明天
    const tomorrow = new Date();
    tomorrow.setDate(tomorrow.getDate() + 1);
    document.getElementById('bookDate').value = tomorrow.toISOString().split('T')[0];
}

数据加载函数

function loadData() {
    const savedOrders = localStorage.getItem('repairOrders');
    if (savedOrders) {
        appState.orders = JSON.parse(savedOrders);
    }
    
    const savedUser = localStorage.getItem('repairUser');
    if (savedUser) {
        appState.user = JSON.parse(savedUser);
    }
}

数据保存函数

function saveData() {
    localStorage.setItem('repairOrders', JSON.stringify(appState.orders));
    localStorage.setItem('repairUser', JSON.stringify(appState.user));
}

3.2 单页应用视图切换

本系统采用单页应用(SPA)模式,通过CSS类控制视图的显示隐藏:

function switchTab(tab) {
    appState.currentTab = tab;
    
    // 更新导航按钮状态
    document.querySelectorAll('.nav-btn').forEach(btn => {
        btn.classList.toggle('active', btn.dataset.tab === tab);
    });
    
    // 切换视图显示
    document.querySelectorAll('.view').forEach(view => {
        view.classList.toggle('active', view.id === `${tab}-view`);
    });
    
    // 视图特定操作
    if (tab === 'orders') {
        renderOrders();      // 切换到订单视图时重新渲染
    }
    
    if (tab === 'profile') {
        updateProfile();     // 切换到个人中心时更新数据
    }
}

CSS样式控制

.view {
    display: none;  /* 默认隐藏 */
}

.view.active {
    display: block;  /* 显示当前视图 */
    animation: fadeIn 0.3s ease;  /* 淡入动画 */
}

3.3 服务分类渲染

首页展示所有服务分类:

function renderCategories() {
    const grid = document.getElementById('categoryGrid');
    grid.innerHTML = serviceTypes.map(service => `
        <div class="category-card" data-id="${service.id}">
            <div class="category-icon">${service.icon}</div>
            <div class="category-name">${service.name}</div>
            <div class="category-desc">${service.desc}</div>
            <div class="category-price">¥${service.price}起</div>
        </div>
    `).join('');
}

3.4 预约表单提交

预约功能是系统的核心,包含了完整的表单验证:

function submitBooking() {
    // 获取表单数据
    const serviceId = appState.selectedService?.id;
    const problemDesc = document.getElementById('problemDesc').value.trim();
    const bookDate = document.getElementById('bookDate').value;
    const bookTime = document.getElementById('bookTime').value;
    const userName = document.getElementById('userName').value.trim();
    const userPhone = document.getElementById('userPhone').value.trim();
    const userAddress = document.getElementById('userAddress').value.trim();
    const remark = document.getElementById('remark').value.trim();
    
    // 表单验证
    if (!serviceId) {
        showToast('请选择服务类型');
        return;
    }
    
    if (!problemDesc) {
        showToast('请描述您的问题');
        return;
    }
    
    if (!bookDate) {
        showToast('请选择预约日期');
        return;
    }
    
    if (!userName || !userPhone || !userAddress) {
        showToast('请填写完整的联系信息');
        return;
    }
    
    // 手机号格式验证
    if (!/^1[3-9]\d{9}$/.test(userPhone)) {
        showToast('请输入正确的手机号');
        return;
    }
    
    // 更新用户信息
    appState.user.name = userName;
    appState.user.phone = userPhone;
    
    // 创建订单对象
    const service = serviceTypes.find(s => s.id === serviceId);
    const order = {
        id: generateOrderId(),
        serviceId: service.id,
        serviceName: service.name,
        serviceIcon: service.icon,
        servicePrice: service.price,
        problemDesc,
        bookDate,
        bookTime,
        userName,
        userPhone,
        userAddress,
        remark,
        status: 'pending',
        createTime: new Date().toISOString()
    };
    
    // 保存订单
    appState.orders.unshift(order);  // 新订单添加到列表头部
    saveData();
    
    // 清空表单
    clearBookingForm();
    
    // 显示成功提示
    showToast('预约成功!等待师傅接单');
    
    // 切换到订单视图
    switchTab('orders');
}

订单ID生成

function generateOrderId() {
    const timestamp = Date.now().toString(36);  // 时间戳转为36进制
    const random = Math.random().toString(36).substring(2, 6).toUpperCase();
    return `ORD${timestamp}${random}`;  // 格式:ORD + 时间戳 + 随机4位
}

3.5 服务选择交互

用户点击服务选项时的处理:

function selectService(serviceId) {
    // 保存选中的服务
    appState.selectedService = serviceTypes.find(s => s.id === serviceId);
    
    // 更新UI选中状态
    document.querySelectorAll('.service-option').forEach(opt => {
        opt.classList.toggle('selected', parseInt(opt.dataset.id) === serviceId);
    });
}

事件委托处理服务选择:

document.getElementById('serviceSelector').addEventListener('click', (e) => {
    const option = e.target.closest('.service-option');
    if (option) {
        selectService(parseInt(option.dataset.id));
    }
});

3.6 订单列表渲染

订单列表根据筛选条件动态渲染:

function renderOrders() {
    const container = document.getElementById('ordersList');
    let filteredOrders = appState.orders;
    
    // 应用筛选条件
    if (appState.currentFilter !== 'all') {
        filteredOrders = filteredOrders.filter(o => o.status === appState.currentFilter);
    }
    
    // 空状态处理
    if (filteredOrders.length === 0) {
        container.innerHTML = `
            <div class="empty-orders">
                <div class="empty-orders-icon">📋</div>
                <p>暂无订单</p>
            </div>
        `;
        return;
    }
    
    // 渲染订单卡片
    container.innerHTML = filteredOrders.map(order => `
        <div class="order-card" data-id="${order.id}">
            <div class="order-header">
                <span class="order-id">${order.id}</span>
                <span class="order-status ${order.status}">${getStatusText(order.status)}</span>
            </div>
            <div class="order-content">
                <div class="order-icon">${order.serviceIcon}</div>
                <div class="order-info">
                    <div class="order-service">${order.serviceName}</div>
                    <div class="order-desc">${order.problemDesc}</div>
                    <div class="order-time">${formatDate(order.bookDate)} ${order.bookTime}</div>
                </div>
            </div>
            <div class="order-footer">
                <span class="order-price">¥${order.servicePrice}</span>
                <div class="order-actions">
                    ${getOrderActions(order)}
                </div>
            </div>
        </div>
    `).join('');
}

3.7 订单状态管理

订单状态定义与流转:

// 状态常量
const statusMap = {
    pending: '待接单',      // 等待师傅接单
    confirmed: '已接单',    // 师傅已接单
    completed: '已完成',    // 服务完成
    cancelled: '已取消'     // 订单取消
};

// 根据状态显示操作按钮
function getOrderActions(order) {
    if (order.status === 'pending') {
        return `<button class="order-action-btn danger" data-action="cancel">取消订单</button>`;
    } else if (order.status === 'confirmed') {
        return `<button class="order-action-btn" data-action="complete">确认完成</button>`;
    } else if (order.status === 'completed' || order.status === 'cancelled') {
        return `<button class="order-action-btn danger" data-action="delete">删除</button>`;
    }
    return '';
}

取消订单

function cancelOrder(orderId) {
    if (confirm('确定要取消这个订单吗?')) {
        updateOrderStatus(orderId, 'cancelled');
    }
}

更新订单状态

function updateOrderStatus(orderId, status) {
    const order = appState.orders.find(o => o.id === orderId);
    if (order) {
        order.status = status;
        saveData();
        renderOrders();
        showToast(status === 'completed' ? '订单已完成' : '状态已更新');
    }
}

3.8 订单详情弹窗

点击订单卡片显示详情:

function showOrderDetail(orderId) {
    const order = appState.orders.find(o => o.id === orderId);
    if (!order) return;
    
    const modal = document.getElementById('orderModal');
    const body = document.getElementById('orderModalBody');
    
    body.innerHTML = `
        <div class="order-detail-row">
            <span class="order-detail-label">订单编号</span>
            <span class="order-detail-value">${order.id}</span>
        </div>
        <div class="order-detail-row">
            <span class="order-detail-label">服务类型</span>
            <span class="order-detail-value">${order.serviceIcon} ${order.serviceName}</span>
        </div>
        <div class="order-detail-row">
            <span class="order-detail-label">预约时间</span>
            <span class="order-detail-value">${formatDate(order.bookDate)} ${order.bookTime}</span>
        </div>
        <div class="order-detail-row">
            <span class="order-detail-label">联系地址</span>
            <span class="order-detail-value">${order.userAddress}</span>
        </div>
        <div class="order-detail-row">
            <span class="order-detail-label">联系电话</span>
            <span class="order-detail-value">${order.userPhone}</span>
        </div>
        <div class="order-detail-row">
            <span class="order-detail-label">订单状态</span>
            <span class="order-detail-value order-status ${order.status}">${getStatusText(order.status)}</span>
        </div>
        <div class="order-detail-row">
            <span class="order-detail-label">订单金额</span>
            <span class="order-detail-value" style="color: var(--danger);">¥${order.servicePrice}</span>
        </div>
        <div class="order-detail-row">
            <span class="order-detail-label">问题描述</span>
            <span class="order-detail-value">${order.problemDesc}</span>
        </div>
        ${order.remark ? `
        <div class="order-detail-row">
            <span class="order-detail-label">备注</span>
            <span class="order-detail-value">${order.remark}</span>
        </div>
        ` : ''}
        <div class="order-detail-row">
            <span class="order-detail-label">下单时间</span>
            <span class="order-detail-value">${formatDateTime(order.createTime)}</span>
        </div>
    `;
    
    modal.classList.add('active');
}

3.9 个人中心统计

实时计算用户数据:

function updateProfile() {
    // 更新用户信息显示
    document.getElementById('profileName').textContent = appState.user.name;
    document.getElementById('profilePhone').textContent = appState.user.phone || '暂无绑定手机';
    
    // 计算统计数据
    const totalOrders = appState.orders.length;
    const completedOrders = appState.orders.filter(o => o.status === 'completed').length;
    const totalSpent = appState.orders
        .filter(o => o.status === 'completed')
        .reduce((sum, o) => sum + o.servicePrice, 0);
    
    // 更新显示
    document.getElementById('totalOrders').textContent = totalOrders;
    document.getElementById('completedOrders').textContent = completedOrders;
    document.getElementById('totalSpent').textContent = `¥${totalSpent}`;
}

3.10 Toast提示组件

统一的用户反馈机制:

function showToast(message) {
    const toast = document.getElementById('toast');
    toast.querySelector('.toast-message').textContent = message;
    toast.classList.add('show');
    
    setTimeout(() => {
        toast.classList.remove('show');
    }, 3000);
}

四、前端样式设计

4.1 主题色彩系统

:root {
    /* 主色调 - 专业蓝 */
    --primary: #2563eb;
    --primary-light: #3b82f6;
    --primary-dark: #1d4ed8;
    
    /* 辅助色 */
    --success: #10b981;    /* 成功绿 */
    --warning: #f59e0b;    /* 警告黄 */
    --danger: #ef4444;     /* 危险红 */
    --info: #06b6d4;       /* 信息青 */
    
    /* 中性色 */
    --bg-primary: #f8fafc;
    --bg-secondary: #ffffff;
    --bg-tertiary: #f1f5f9;
    --text-primary: #1e293b;
    --text-secondary: #64748b;
    --text-light: #94a3b8;
    --border: #e2e8f0;
}

4.2 卡片阴影效果

--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow-md: 0 4px 6px rgba(0,0,0,0.07);
--shadow-lg: 0 10px 15px rgba(0,0,0,0.1);

4.3 订单状态样式

.order-status.pending {
    background: rgba(245,158,11,0.1);
    color: var(--warning);
}

.order-status.confirmed {
    background: rgba(37,99,235,0.1);
    color: var(--primary);
}

.order-status.completed {
    background: rgba(16,185,129,0.1);
    color: var(--success);
}

.order-status.cancelled {
    background: rgba(239,68,68,0.1);
    color: var(--danger);
}

4.4 响应式布局

@media (max-width: 768px) {
    .header-content {
        flex-direction: column;
        gap: var(--spacing-md);
    }
    
    .nav {
        width: 100%;
        overflow-x: auto;
        justify-content: flex-start;
    }
    
    .category-grid {
        grid-template-columns: repeat(2, 1fr);
    }
    
    .service-selector {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 480px) {
    .category-grid {
        grid-template-columns: 1fr;
    }
    
    .advantage-grid {
        grid-template-columns: 1fr;
    }
}

五、事件监听系统

5.1 完整事件绑定

function setupEventListeners() {
    // 导航按钮点击
    document.querySelectorAll('.nav-btn').forEach(btn => {
        btn.addEventListener('click', () => switchTab(btn.dataset.tab));
    });
    
    // 服务选择点击
    document.getElementById('serviceSelector').addEventListener('click', (e) => {
        const option = e.target.closest('.service-option');
        if (option) {
            selectService(parseInt(option.dataset.id));
        }
    });
    
    // 提交预约
    document.getElementById('submitBook').addEventListener('click', submitBooking);
    
    // 订单筛选
    document.querySelectorAll('.filter-btn').forEach(btn => {
        btn.addEventListener('click', () => filterOrders(btn.dataset.status));
    });
    
    // 关闭弹窗
    document.getElementById('closeOrderModal').addEventListener('click', closeOrderModal);
    document.getElementById('orderModal').addEventListener('click', (e) => {
        if (e.target === document.getElementById('orderModal')) {
            closeOrderModal();
        }
    });
}

5.2 订单卡片事件委托

订单列表中的卡片和按钮使用事件委托处理:

// 渲染完成后绑定事件
container.querySelectorAll('.order-card').forEach(card => {
    card.addEventListener('click', (e) => {
        // 点击非按钮区域显示详情
        if (!e.target.classList.contains('order-action-btn')) {
            showOrderDetail(card.dataset.id);
        }
    });
});

// 操作按钮事件
container.querySelectorAll('.order-action-btn').forEach(btn => {
    btn.addEventListener('click', (e) => {
        e.stopPropagation();  // 阻止冒泡
        const orderId = btn.closest('.order-card').dataset.id;
        const action = btn.dataset.action;
        
        if (action === 'complete') {
            updateOrderStatus(orderId, 'completed');
        } else if (action === 'cancel') {
            cancelOrder(orderId);
        } else if (action === 'delete') {
            deleteOrder(orderId);
        }
    });
});

六、数据管理

6.1 LocalStorage数据结构

Key 数据类型 说明
repairOrders Array 订单列表
repairUser Object 用户信息

6.2 订单数据流转

用户提交预约
    ↓
创建订单对象 (status: 'pending')
    ↓
存入 localStorage
    ↓
渲染订单列表
    ↓
订单状态变更 → 更新 localStorage → 重新渲染

七、代码优化建议

7.1 错误处理增强

function loadData() {
    try {
        const savedOrders = localStorage.getItem('repairOrders');
        if (savedOrders) {
            appState.orders = JSON.parse(savedOrders);
        }
        
        const savedUser = localStorage.getItem('repairUser');
        if (savedUser) {
            appState.user = JSON.parse(savedUser);
        }
    } catch (error) {
        console.error('加载数据失败:', error);
        appState.orders = [];
        appState.user = { name: '访客用户', phone: '' };
    }
}

7.2 防抖优化搜索

如果未来添加搜索功能,可以使用防抖:

function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

7.3 扩展性设计

添加新服务类型只需修改 serviceTypes 数组:

const serviceTypes = [
    // 现有服务...
    { id: 7, name: '新服务', icon: '🆕', price: 120, desc: '服务描述' }
];

八、总结与展望

8.1 项目成果

上门维修预约系统成功实现了以下功能:

  1. 完整的预约流程:从服务选择到表单填写、提交
  2. 订单管理系统:查看、筛选、取消、完成、删除订单
  3. 个人中心:用户信息、统计面板、菜单导航
  4. 数据持久化:localStorage存储所有数据
  5. 专业UI设计:蓝色主题、卡片布局、状态色彩

8.2 技术亮点

  • 单页应用:无刷新切换视图,提升用户体验
  • 状态驱动:清晰的state管理,状态变化驱动UI更新
  • 事件委托:高效的事件处理机制
  • 表单验证:完整的客户端验证逻辑
  • 本地存储:无需后端即可持久化数据

8.3 未来规划

优先级 功能 说明
师傅端 师傅查看和接单
地图定位 选择上门地址
支付功能 在线支付订单
评价系统 服务完成后评分
优惠券 优惠活动
消息推送 订单状态通知

8.4 技术拓展

未来可以考虑引入以下技术:

  • WebSocket:实时订单状态更新
  • 地图API:地址选择和定位
  • 微信/支付宝支付:在线支付
  • 信鸽推送:消息通知
  • 数据库:云端数据同步

附录

核心文件列表

文件 行数 说明
index.html ~260 页面结构,包含所有视图和组件
style.css ~880 样式文件,包含布局和动画
js/app.js ~480 核心逻辑,状态管理和事件处理

服务价格表

服务类型 基础价格 说明
家电维修 ¥80起 根据具体电器可能增加
水电安装 ¥100起 根据材料和工作量
管道疏通 ¥90起 根据堵塞程度
开锁换锁 ¥60起 根据锁具类型
灯具安装 ¥50起 根据灯具复杂度
墙面修补 ¥70起 根据修补面积

浏览器兼容性

浏览器 最低版本
Chrome 80+
Firefox 75+
Safari 13+
Edge 80+

参考资料

  1. MDN Web Docs - localStorage
  2. MDN Web Docs - 表单验证
  3. Electron官方文档
  4. HarmonyOS开发者文档
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐