移动端工单系统完整实现方案

Vibe Coding 使用 AI 回答

代码问题:

实现一个支持移动端的工单系统

代码解决方案:

我来为你设计一个完整的移动端工单系统。这个系统将包含用户和管理员两个视角,支持工单的创建、处理、跟踪等核心功能。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>移动工单系统</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * {
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
        }
        
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            background-color: #f5f7fa;
            overflow-x: hidden;
        }
        
        .app-container {
            max-width: 768px;
            margin: 0 auto;
            background-color: white;
            min-height: 100vh;
            position: relative;
            box-shadow: 0 0 20px rgba(0,0,0,0.05);
        }
        
        .page {
            display: none;
            animation: slideIn 0.3s ease-out;
        }
        
        .page.active {
            display: block;
        }
        
        @keyframes slideIn {
            from {
                opacity: 0;
                transform: translateX(20px);
            }
            to {
                opacity: 1;
                transform: translateX(0);
            }
        }
        
        .ticket-card {
            transition: all 0.3s ease;
            border: 1px solid #e5e7eb;
        }
        
        .ticket-card:active {
            transform: scale(0.98);
            background-color: #f9fafb;
        }
        
        .status-badge {
            display: inline-flex;
            align-items: center;
            padding: 4px 8px;
            border-radius: 12px;
            font-size: 12px;
            font-weight: 500;
        }
        
        .status-pending {
            background-color: #fef3c7;
            color: #92400e;
        }
        
        .status-processing {
            background-color: #dbeafe;
            color: #1e40af;
        }
        
        .status-completed {
            background-color: #d1fae5;
            color: #065f46;
        }
        
        .status-closed {
            background-color: #f3f4f6;
            color: #6b7280;
        }
        
        .bottom-nav {
            position: fixed;
            bottom: 0;
            left: 50%;
            transform: translateX(-50%);
            width: 100%;
            max-width: 768px;
            background-color: white;
            border-top: 1px solid #e5e7eb;
            z-index: 1000;
        }
        
        .nav-item {
            flex: 1;
            text-align: center;
            padding: 8px 0;
            transition: color 0.3s ease;
        }
        
        .nav-item.active {
            color: #3b82f6;
        }
        
        .fab {
            position: fixed;
            bottom: 80px;
            right: 20px;
            width: 56px;
            height: 56px;
            border-radius: 50%;
            background-color: #3b82f6;
            color: white;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
            transition: all 0.3s ease;
            z-index: 100;
        }
        
        .fab:active {
            transform: scale(0.9);
        }
        
        .input-group {
            position: relative;
            margin-bottom: 16px;
        }
        
        .input-group input,
        .input-group textarea,
        .input-group select {
            width: 100%;
            padding: 12px 16px;
            border: 1px solid #d1d5db;
            border-radius: 8px;
            font-size: 16px;
            transition: all 0.3s ease;
        }
        
        .input-group input:focus,
        .input-group textarea:focus,
        .input-group select:focus {
            outline: none;
            border-color: #3b82f6;
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
        }
        
        .input-group label {
            position: absolute;
            left: 16px;
            top: 12px;
            color: #6b7280;
            transition: all 0.3s ease;
            pointer-events: none;
        }
        
        .input-group input:focus ~ label,
        .input-group input:not(:placeholder-shown) ~ label,
        .input-group textarea:focus ~ label,
        .input-group textarea:not(:placeholder-shown) ~ label {
            top: -8px;
            left: 12px;
            font-size: 12px;
            background-color: white;
            padding: 0 4px;
            color: #3b82f6;
        }
        
        .priority-high {
            color: #dc2626;
        }
        
        .priority-medium {
            color: #d97706;
        }
        
        .priority-low {
            color: #059669;
        }
        
        .skeleton {
            background: linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%);
            background-size: 200% 100%;
            animation: loading 1.5s infinite;
        }
        
        @keyframes loading {
            0% {
                background-position: 200% 0;
            }
            100% {
                background-position: -200% 0;
            }
        }
        
        .pull-to-refresh {
            position: absolute;
            top: -60px;
            left: 50%;
            transform: translateX(-50%);
            color: #6b7280;
            font-size: 14px;
            transition: all 0.3s ease;
        }
        
        .pull-to-refresh.active {
            top: 20px;
        }
        
        .toast {
            position: fixed;
            top: 50px;
            left: 50%;
            transform: translateX(-50%) translateY(-100px);
            background-color: #1f2937;
            color: white;
            padding: 12px 24px;
            border-radius: 8px;
            font-size: 14px;
            z-index: 2000;
            transition: transform 0.3s ease;
        }
        
        .toast.show {
            transform: translateX(-50%) translateY(0);
        }
    </style>
</head>
<body>
    <div class="app-container">
        <!-- 登录页面 -->
        <div id="loginPage" class="page active">
            <div class="h-screen flex flex-col justify-center items-center px-6">
                <div class="w-full max-w-sm">
                    <div class="text-center mb-8">
                        <div class="w-20 h-20 bg-blue-500 rounded-2xl flex items-center justify-center mx-auto mb-4">
                            <i class="fas fa-ticket-alt text-white text-3xl"></i>
                        </div>
                        <h1 class="text-2xl font-bold text-gray-900">移动工单系统</h1>
                        <p class="text-gray-600 mt-2">欢迎使用,请登录</p>
                    </div>
                    
                    <form id="loginForm" class="space-y-4">
                        <div class="input-group">
                            <input type="text" id="username" placeholder=" " required>
                            <label>用户名</label>
                        </div>
                        <div class="input-group">
                            <input type="password" id="password" placeholder=" " required>
                            <label>密码</label>
                        </div>
                        
                        <div class="flex items-center space-x-4">
                            <label class="flex items-center">
                                <input type="radio" name="role" value="user" class="mr-2" checked>
                                <span class="text-sm">用户</span>
                            </label>
                            <label class="flex items-center">
                                <input type="radio" name="role" value="admin" class="mr-2">
                                <span class="text-sm">管理员</span>
                            </label>
                        </div>
                        
                        <button type="submit" class="w-full bg-blue-500 text-white py-3 rounded-lg font-medium transition-all hover:bg-blue-600 active:scale-95">
                            登录
                        </button>
                    </form>
                </div>
            </div>
        </div>

        <!-- 用户端 - 工单列表 -->
        <div id="userTicketListPage" class="page">
            <div class="bg-white border-b">
                <div class="px-4 py-3 flex items-center justify-between">
                    <h1 class="text-lg font-semibold">我的工单</h1>
                    <button onclick="logout()" class="text-gray-600">
                        <i class="fas fa-sign-out-alt"></i>
                    </button>
                </div>
            </div>
            
            <div class="relative">
                <div id="pullToRefresh" class="pull-to-refresh">
                    <i class="fas fa-arrow-down mr-2"></i>下拉刷新
                </div>
                
                <div id="ticketList" class="p-4 space-y-3">
                    <!-- 工单列表将在这里动态生成 -->
                </div>
            </div>
            
            <button onclick="showCreateTicketPage()" class="fab">
                <i class="fas fa-plus text-xl"></i>
            </button>
        </div>

        <!-- 用户端 - 创建工单 -->
        <div id="createTicketPage" class="page">
            <div class="bg-white border-b">
                <div class="px-4 py-3 flex items-center">
                    <button onclick="showUserTicketList()" class="mr-3">
                        <i class="fas fa-arrow-left"></i>
                    </button>
                    <h1 class="text-lg font-semibold">创建工单</h1>
                </div>
            </div>
            
            <form id="createTicketForm" class="p-4 space-y-4">
                <div class="input-group">
                    <select id="ticketType" required>
                        <option value="">请选择工单类型</option>
                        <option value="repair">维修申请</option>
                        <option value="complaint">投诉建议</option>
                        <option value="consultation">咨询求助</option>
                        <option value="other">其他</option>
                    </select>
                    <label>工单类型</label>
                </div>
                
                <div class="input-group">
                    <select id="ticketPriority" required>
                        <option value="">请选择优先级</option>
                        <option value="low">低</option>
                        <option value="medium">中</option>
                        <option value="high">高</option>
                    </select>
                    <label>优先级</label>
                </div>
                
                <div class="input-group">
                    <input type="text" id="ticketTitle" placeholder=" " required>
                    <label>工单标题</label>
                </div>
                
                <div class="input-group">
                    <textarea id="ticketContent" rows="4" placeholder=" " required></textarea>
                    <label>问题描述</label>
                </div>
                
                <div class="input-group">
                    <input type="file" id="ticketAttachment" accept="image/*">
                    <label>添加图片(可选)</label>
                </div>
                
                <button type="submit" class="w-full bg-blue-500 text-white py-3 rounded-lg font-medium transition-all hover:bg-blue-600 active:scale-95">
                    提交工单
                </button>
            </form>
        </div>

        <!-- 用户端 - 工单详情 -->
        <div id="ticketDetailPage" class="page">
            <div class="bg-white border-b">
                <div class="px-4 py-3 flex items-center">
                    <button onclick="showUserTicketList()" class="mr-3">