Html+css+js 写维修登记管理系统

19 阅读12分钟

图:

image.png

代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>维修登记管理系统 - 增强版</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }

        :root {
            --primary: #3498db;
            --secondary: #2c3e50;
            --success: #2ecc71;
            --warning: #f39c12;
            --danger: #e74c3c;
            --light: #ecf0f1;
            --dark: #34495e;
            --gray: #95a5a6;
        }

        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
        }

        header {
            background: white;
            border-radius: 10px;
            padding: 20px;
            margin-bottom: 20px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 15px;
        }

        .logo i {
            font-size: 28px;
            color: var(--primary);
        }

        .logo h1 {
            color: var(--secondary);
            font-size: 24px;
            font-weight: 700;
        }

        .stats {
            display: flex;
            gap: 15px;
        }

        .stat-card {
            background: var(--light);
            padding: 10px 15px;
            border-radius: 8px;
            text-align: center;
            min-width: 100px;
        }

        .stat-card h3 {
            font-size: 24px;
            color: var(--secondary);
        }

        .stat-card p {
            font-size: 12px;
            color: var(--gray);
            margin-top: 5px;
        }

        .main-content {
            display: grid;
            grid-template-columns: 1fr 2fr;
            gap: 20px;
        }

        .card {
            background: white;
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
        }

        .card-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
            padding-bottom: 15px;
            border-bottom: 1px solid #eee;
        }

        .card-title {
            font-size: 18px;
            font-weight: 600;
            color: var(--secondary);
        }

        .form-group {
            margin-bottom: 15px;
            position: relative;
        }

        label {
            display: block;
            margin-bottom: 5px;
            color: var(--dark);
            font-weight: 500;
            font-size: 14px;
        }

        input, select, textarea {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 14px;
            transition: all 0.3s;
        }

        input:focus, select:focus, textarea:focus {
            outline: none;
            border-color: var(--primary);
            box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
        }

        textarea {
            min-height: 80px;
            resize: vertical;
        }

        /* 智能输入框样式 */
        .smart-input input {
            padding-right: 35px;
        }

        .clear-btn {
            position: absolute;
            right: 8px;
            top: 50%;
            transform: translateY(-50%);
            background: none;
            border: none;
            color: var(--gray);
            cursor: pointer;
            padding: 5px;
            display: none;
        }

        .smart-input input:not(:placeholder-shown) ~ .clear-btn {
            display: block;
        }

        .history-hint {
            font-size: 11px;
            color: var(--gray);
            margin-top: 3px;
            font-style: italic;
        }

        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            font-size: 14px;
            transition: all 0.3s;
            display: inline-flex;
            align-items: center;
            gap: 8px;
        }

        .btn-primary {
            background: var(--primary);
            color: white;
        }

        .btn-primary:hover {
            background: #2980b9;
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(52, 152, 219, 0.3);
        }

        .btn-success {
            background: var(--success);
            color: white;
        }

        .btn-warning {
            background: var(--warning);
            color: white;
        }

        .btn-danger {
            background: var(--danger);
            color: white;
        }

        .btn-sm {
            padding: 5px 10px;
            font-size: 12px;
        }

        .btn-secondary {
            background: var(--light);
            color: var(--dark);
            border: 1px solid #ddd;
        }

        .btn-secondary:hover {
            background: #d5dbdb;
        }

        .btn-info {
            background: #17a2b8;
            color: white;
        }

        .btn-info:hover {
            background: #138496;
        }

        .search-box {
            display: flex;
            gap: 10px;
            margin-bottom: 15px;
        }

        .search-box input {
            flex: 1;
        }

        .status-filter {
            display: flex;
            gap: 10px;
            margin-bottom: 15px;
            flex-wrap: wrap;
        }

        .filter-btn {
            padding: 6px 12px;
            border-radius: 20px;
            background: var(--light);
            border: 1px solid #ddd;
            cursor: pointer;
            font-size: 13px;
            transition: all 0.3s;
        }

        .filter-btn.active {
            background: var(--primary);
            color: white;
            border-color: var(--primary);
        }

        .tickets-list {
            max-height: 500px;
            overflow-y: auto;
            padding-right: 5px;
        }

        .ticket-item {
            background: #f9f9f9;
            border-radius: 8px;
            padding: 15px;
            margin-bottom: 12px;
            border-left: 4px solid var(--gray);
            transition: all 0.3s;
        }

        .ticket-item:hover {
            transform: translateX(5px);
            box-shadow: 0 3px 8px rgba(0,0,0,0.1);
        }

        .ticket-item.pending { border-left-color: var(--warning); }
        .ticket-item.progress { border-left-color: var(--primary); }
        .ticket-item.completed { border-left-color: var(--success); }
        .ticket-item.canceled { border-left-color: var(--danger); }

        .ticket-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
            flex-wrap: wrap;
            gap: 10px;
        }

        .ticket-title {
            font-weight: 600;
            color: var(--secondary);
            font-size: 16px;
        }

        .ticket-number {
            background: var(--secondary);
            color: white;
            padding: 4px 8px;
            border-radius: 4px;
            font-family: 'Courier New', monospace;
            font-weight: bold;
            font-size: 14px;
            letter-spacing: 1px;
        }

        .ticket-status {
            padding: 3px 8px;
            border-radius: 12px;
            font-size: 12px;
            font-weight: 600;
        }

        .status-pending { background: #ffeaa7; color: #d35400; }
        .status-progress { background: #74b9ff; color: #0984e3; }
        .status-completed { background: #55efc4; color: #00b894; }
        .status-canceled { background: #ff7675; color: #d63031; }

        .ticket-body {
            font-size: 14px;
            color: #555;
            margin-bottom: 10px;
            line-height: 1.5;
        }

        .ticket-meta {
            display: flex;
            justify-content: space-between;
            font-size: 12px;
            color: var(--gray);
            margin-top: 8px;
            padding-top: 8px;
            border-top: 1px dashed #eee;
            flex-wrap: wrap;
            gap: 10px;
        }

        .ticket-actions {
            display: flex;
            gap: 8px;
            margin-top: 10px;
            flex-wrap: wrap;
        }

        .empty-state {
            text-align: center;
            padding: 40px 20px;
            color: var(--gray);
        }

        .empty-state i {
            font-size: 48px;
            margin-bottom: 15px;
            opacity: 0.5;
        }

        .notification {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px 25px;
            background: white;
            border-radius: 8px;
            box-shadow: 0 5px 15px rgba(0,0,0,0.2);
            z-index: 1000;
            transform: translateX(150%);
            transition: transform 0.3s ease-out;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .notification.show {
            transform: translateX(0);
        }

        .notification.success {
            border-left: 4px solid var(--success);
        }

        .notification.error {
            border-left: 4px solid var(--danger);
        }

        .history-management {
            margin-top: 10px;
            padding: 10px;
            background: #f8f9fa;
            border-radius: 6px;
            font-size: 12px;
        }

        .history-management button {
            margin-right: 8px;
            margin-top: 5px;
        }

        .history-list {
            margin-top: 8px;
            display: none;
            background: white;
            border: 1px solid #ddd;
            border-radius: 4px;
            max-height: 150px;
            overflow-y: auto;
            padding: 5px;
        }

        .history-list.show {
            display: block;
        }

        .history-item {
            padding: 5px 8px;
            cursor: pointer;
            border-radius: 3px;
            transition: background 0.2s;
        }

        .history-item:hover {
            background: var(--light);
        }

        .history-item .count {
            float: right;
            color: var(--gray);
            font-size: 11px;
        }

        /* 模态框 */
        .modal {
            display: none;
            position: fixed;
            z-index: 2000;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.5);
            animation: fadeIn 0.3s;
        }

        .modal-content {
            background-color: white;
            margin: 5% auto;
            padding: 20px;
            border-radius: 10px;
            width: 90%;
            max-width: 500px;
            box-shadow: 0 5px 20px rgba(0,0,0,0.3);
            animation: slideIn 0.3s;
        }

        .modal-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid #eee;
        }

        .modal-header h3 {
            color: var(--secondary);
        }

        .close-modal {
            font-size: 28px;
            font-weight: bold;
            color: var(--gray);
            cursor: pointer;
            border: none;
            background: none;
        }

        .close-modal:hover {
            color: var(--danger);
        }

        .copy-info-box {
            background: #f8f9fa;
            padding: 15px;
            border-radius: 6px;
            margin: 15px 0;
            border: 1px solid #ddd;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            line-height: 1.6;
            max-height: 300px;
            overflow-y: auto;
        }

        .modal-actions {
            display: flex;
            gap: 10px;
            justify-content: flex-end;
            margin-top: 15px;
        }

        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }

        @keyframes slideIn {
            from { transform: translateY(-50px); opacity: 0; }
            to { transform: translateY(0); opacity: 1; }
        }

        @media (max-width: 768px) {
            .main-content {
                grid-template-columns: 1fr;
            }

            header {
                flex-direction: column;
                gap: 15px;
            }

            .stats {
                width: 100%;
                justify-content: center;
            }

            .ticket-header {
                flex-direction: column;
                align-items: flex-start;
            }

            .modal-content {
                width: 95%;
                margin: 10% auto;
            }
        }

        /* Scrollbar styling */
        .tickets-list::-webkit-scrollbar,
        .history-list::-webkit-scrollbar,
        .copy-info-box::-webkit-scrollbar {
            width: 6px;
        }

        .tickets-list::-webkit-scrollbar-track,
        .history-list::-webkit-scrollbar-track,
        .copy-info-box::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 10px;
        }

        .tickets-list::-webkit-scrollbar-thumb,
        .history-list::-webkit-scrollbar-thumb,
        .copy-info-box::-webkit-scrollbar-thumb {
            background: #bdc3c7;
            border-radius: 10px;
        }

        .tickets-list::-webkit-scrollbar-thumb:hover,
        .history-list::-webkit-scrollbar-thumb:hover,
        .copy-info-box::-webkit-scrollbar-thumb:hover {
            background: #95a5a6;
        }

        .search-hint {
            font-size: 12px;
            color: var(--gray);
            margin-top: 5px;
            font-style: italic;
        }

        /* 打印按钮组 */
        .print-actions {
            display: flex;
            gap: 5px;
            margin-top: 8px;
        }

        .copy-success {
            color: var(--success);
            font-size: 12px;
            margin-left: 10px;
            display: none;
        }

        .copy-success.show {
            display: inline;
            animation: fadeIn 0.3s;
        }

        /* 打印专用样式 - 使用新窗口方法,避免CSS冲突 */
        .print-preview {
            display: none;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <div class="logo">
                <i class="fas fa-tools"></i>
                <h1>维修登记管理系统</h1>
            </div>
            <div class="stats">
                <div class="stat-card">
                    <h3 id="total-count">0</h3>
                    <p>总工单</p>
                </div>
                <div class="stat-card">
                    <h3 id="pending-count">0</h3>
                    <p>待处理</p>
                </div>
                <div class="stat-card">
                    <h3 id="progress-count">0</h3>
                    <p>维修中</p>
                </div>
            </div>
        </header>

        <div class="main-content">
            <div class="left-panel">
                <div class="card">
                    <div class="card-header">
                        <div class="card-title">提交维修单</div>
                        <i class="fas fa-clipboard-list" style="color: var(--primary);"></i>
                    </div>
                    <form id="repair-form">
                        <!-- 设备名称 - 智能输入 -->
                        <div class="form-group">
                            <label for="device">设备名称 <span style="color: var(--gray); font-weight: normal;">(可选,支持历史记录)</span></label>
                            <div class="smart-input">
                                <input type="text" id="device" placeholder="输入或选择设备名称" list="device-list" autocomplete="off">
                                <button type="button" class="clear-btn" onclick="clearInput('device')" title="清空">
                                    <i class="fas fa-times"></i>
                                </button>
                            </div>
                            <datalist id="device-list"></datalist>
                            <div class="history-hint">💡 提示:输入时会自动匹配历史记录,也可以输入新设备名称</div>
                            <div class="history-management">
                                <button type="button" class="btn btn-sm btn-secondary" onclick="toggleHistory('device')">
                                    <i class="fas fa-history"></i> 查看历史
                                </button>
                                <button type="button" class="btn btn-sm btn-danger" onclick="clearHistory('device')">
                                    <i class="fas fa-trash"></i> 清空历史
                                </button>
                                <div id="device-history-list" class="history-list"></div>
                            </div>
                        </div>

                        <!-- 故障描述 - 智能输入 -->
                        <div class="form-group">
                            <label for="problem">故障描述 <span style="color: var(--gray); font-weight: normal;">(可选,支持历史记录)</span></label>
                            <div class="smart-input">
                                <input type="text" id="problem" placeholder="输入或选择故障描述" list="problem-list" autocomplete="off">
                                <button type="button" class="clear-btn" onclick="clearInput('problem')" title="清空">
                                    <i class="fas fa-times"></i>
                                </button>
                            </div>
                            <datalist id="problem-list"></datalist>
                            <div class="history-hint">💡 提示:输入时会自动匹配历史故障,也可以输入新的故障描述</div>
                            <div class="history-management">
                                <button type="button" class="btn btn-sm btn-secondary" onclick="toggleHistory('problem')">
                                    <i class="fas fa-history"></i> 查看历史
                                </button>
                                <button type="button" class="btn btn-sm btn-danger" onclick="clearHistory('problem')">
                                    <i class="fas fa-trash"></i> 清空历史
                                </button>
                                <div id="problem-history-list" class="history-list"></div>
                            </div>
                        </div>

                        <!-- 联系人信息(必填) -->
                        <div class="form-group">
                            <label for="contact">联系人 <span style="color: var(--danger);">*</span></label>
                            <input type="text" id="contact" placeholder="请输入您的姓名" required>
                        </div>
                        <div class="form-group">
                            <label for="phone">联系电话 <span style="color: var(--danger);">*</span></label>
                            <input type="tel" id="phone" placeholder="请输入联系电话" required>
                        </div>
                        <div class="form-group">
                            <label for="location">设备位置 <span style="color: var(--danger);">*</span></label>
                            <input type="text" id="location" placeholder="如:办公室A区3楼" required>
                        </div>
                        <div class="form-group">
                            <label for="urgency">紧急程度 <span style="color: var(--danger);">*</span></label>
                            <select id="urgency" required>
                                <option value="">请选择紧急程度</option>
                                <option value="low"></option>
                                <option value="medium"></option>
                                <option value="high"></option>
                                <option value="critical">紧急</option>
                            </select>
                        </div>
                        <button type="submit" class="btn btn-primary">
                            <i class="fas fa-paper-plane"></i> 提交维修单
                        </button>
                    </form>
                </div>

                <div class="card" style="margin-top: 20px;">
                    <div class="card-header">
                        <div class="card-title">系统说明</div>
                    </div>
                    <div style="font-size: 14px; color: #555; line-height: 1.6;">
                        <p>本系统用于管理设备维修登记,您可以:</p>
                        <ul style="margin: 10px 0 10px 20px;">
                            <li><strong>智能输入:</strong>设备和故障支持历史记录自动匹配</li>
                            <li><strong>灵活填写:</strong>可选择历史记录,也可输入新内容</li>
                            <li><strong>自动生成单号:</strong>格式:前2位字母 + 后2位数字(如:AB12)</li>
                            <li><strong>打印功能:</strong>一键打印维修单,方便上门服务</li>
                            <li><strong>复制信息:</strong>一键复制信息,快速发微信</li>
                        </ul>
                        <p><strong>必填项:</strong>联系人、电话、位置、紧急程度</p>
                        <p><strong>可选项:</strong>设备名称、故障描述(支持历史记录)</p>
                        <p>所有数据保存在浏览器本地,刷新页面不会丢失。</p>
                    </div>
                </div>
            </div>

            <div class="right-panel">
                <div class="card">
                    <div class="card-header">
                        <div class="card-title">维修工单管理</div>
                        <i class="fas fa-tasks" style="color: var(--primary);"></i>
                    </div>

                    <div class="search-box">
                        <input type="text" id="search-input" placeholder="搜索单号、设备、联系人或问题...">
                        <button class="btn btn-primary" id="search-btn">
                            <i class="fas fa-search"></i> 搜索
                        </button>
                    </div>
                    <div class="search-hint">提示:可搜索单号(如 AB12)、设备名、联系人或问题关键词</div>

                    <div class="status-filter">
                        <div class="filter-btn active" data-status="all">全部</div>
                        <div class="filter-btn" data-status="pending">待处理</div>
                        <div class="filter-btn" data-status="progress">维修中</div>
                        <div class="filter-btn" data-status="completed">已完成</div>
                        <div class="filter-btn" data-status="canceled">已取消</div>
                    </div>

                    <div class="tickets-list" id="tickets-list">
                        <div class="empty-state">
                            <i class="fas fa-clipboard-list"></i>
                            <p>暂无维修工单,请提交第一个维修单</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 复制信息模态框 -->
    <div id="copyModal" class="modal">
        <div class="modal-content">
            <div class="modal-header">
                <h3><i class="fas fa-clipboard"></i> 复制信息发微信</h3>
                <button class="close-modal" onclick="closeCopyModal()">&times;</button>
            </div>
            <div class="copy-info-box" id="copyInfoContent"></div>
            <div class="modal-actions">
                <button class="btn btn-secondary" onclick="closeCopyModal()">关闭</button>
                <button class="btn btn-info" onclick="copyToClipboard()">
                    <i class="fas fa-copy"></i> 复制信息
                </button>
                <span id="copySuccess" class="copy-success">✓ 已复制!</span>
            </div>
        </div>
    </div>

    <div class="notification" id="notification">
        <i class="fas fa-check-circle"></i>
        <span id="notification-text">操作成功!</span>
    </div>

    <script>
        // 数据存储
        let repairTickets = JSON.parse(localStorage.getItem('repairTickets')) || [];
        let deviceHistory = JSON.parse(localStorage.getItem('deviceHistory')) || [];
        let problemHistory = JSON.parse(localStorage.getItem('problemHistory')) || [];

        // DOM元素
        const form = document.getElementById('repair-form');
        const ticketsList = document.getElementById('tickets-list');
        const searchInput = document.getElementById('search-input');
        const searchBtn = document.getElementById('search-btn');
        const filterBtns = document.querySelectorAll('.filter-btn');
        const notification = document.getElementById('notification');
        const notificationText = document.getElementById('notification-text');
        const copyModal = document.getElementById('copyModal');
        const copyInfoContent = document.getElementById('copyInfoContent');
        const copySuccess = document.getElementById('copySuccess');

        // 状态映射
        const statusMap = {
            'pending': { label: '待处理', class: 'pending', statusClass: 'status-pending' },
            'progress': { label: '维修中', class: 'progress', statusClass: 'status-progress' },
            'completed': { label: '已完成', class: 'completed', statusClass: 'status-completed' },
            'canceled': { label: '已取消', class: 'canceled', statusClass: 'status-canceled' }
        };

        // 紧急程度映射
        const urgencyMap = {
            'low': '低',
            'medium': '中',
            'high': '高',
            'critical': '紧急'
        };

        // 生成单号函数
        function generateTicketNumber() {
            const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
            const numbers = '0123456789';

            let ticketNumber = '';
            ticketNumber += letters.charAt(Math.floor(Math.random() * letters.length));
            ticketNumber += letters.charAt(Math.floor(Math.random() * letters.length));
            ticketNumber += numbers.charAt(Math.floor(Math.random() * numbers.length));
            ticketNumber += numbers.charAt(Math.floor(Math.random() * numbers.length));

            if (repairTickets.some(t => t.ticketNumber === ticketNumber)) {
                return generateTicketNumber();
            }

            return ticketNumber;
        }

        // 初始化
        document.addEventListener('DOMContentLoaded', () => {
            renderTickets();
            updateStats();
            updateDatalists();

            // 表单提交事件
            form.addEventListener('submit', (e) => {
                e.preventDefault();
                addRepairTicket();
            });

            // 搜索事件
            searchBtn.addEventListener('click', renderTickets);
            searchInput.addEventListener('keyup', (e) => {
                if (e.key === 'Enter') renderTickets();
            });

            // 筛选事件
            filterBtns.forEach(btn => {
                btn.addEventListener('click', () => {
                    filterBtns.forEach(b => b.classList.remove('active'));
                    btn.classList.add('active');
                    renderTickets();
                });
            });
        });

        // 添加维修单
        function addRepairTicket() {
            const ticketNumber = generateTicketNumber();
            const device = document.getElementById('device').value.trim();
            const problem = document.getElementById('problem').value.trim();

            // 保存到历史记录
            if (device && !deviceHistory.includes(device)) {
                deviceHistory.unshift(device);
                if (deviceHistory.length > 20) deviceHistory.pop();
                localStorage.setItem('deviceHistory', JSON.stringify(deviceHistory));
            }

            if (problem && !problemHistory.includes(problem)) {
                problemHistory.unshift(problem);
                if (problemHistory.length > 20) problemHistory.pop();
                localStorage.setItem('problemHistory', JSON.stringify(problemHistory));
            }

            const ticket = {
                id: Date.now(),
                ticketNumber: ticketNumber,
                device: device || '未指定设备',
                problem: problem || '未描述故障',
                contact: document.getElementById('contact').value,
                phone: document.getElementById('phone').value,
                location: document.getElementById('location').value,
                urgency: document.getElementById('urgency').value,
                status: 'pending',
                createdAt: new Date().toLocaleString('zh-CN'),
                updatedAt: new Date().toLocaleString('zh-CN')
            };

            repairTickets.unshift(ticket);
            saveToLocalStorage();
            renderTickets();
            updateStats();
            updateDatalists();

            showNotification(`维修单 ${ticketNumber} 提交成功!`, 'success');
            form.reset();
        }

        // 渲染工单列表
        function renderTickets() {
            const searchTerm = searchInput.value.toLowerCase();
            const activeFilter = document.querySelector('.filter-btn.active').dataset.status;

            let filteredTickets = repairTickets;

            if (activeFilter !== 'all') {
                filteredTickets = filteredTickets.filter(ticket => ticket.status === activeFilter);
            }

            if (searchTerm) {
                filteredTickets = filteredTickets.filter(ticket => {
                    const ticketNumber = (ticket.ticketNumber || '').toString().toLowerCase();
                    const device = (ticket.device || '').toString().toLowerCase();
                    const problem = (ticket.problem || '').toString().toLowerCase();
                    const contact = (ticket.contact || '').toString().toLowerCase();
                    const phone = (ticket.phone || '').toString().toLowerCase();

                    return ticketNumber.includes(searchTerm) ||
                           device.includes(searchTerm) ||
                           problem.includes(searchTerm) ||
                           contact.includes(searchTerm) ||
                           phone.includes(searchTerm);
                });
            }

            if (filteredTickets.length === 0) {
                ticketsList.innerHTML = `
                    <div class="empty-state">
                        <i class="fas fa-search"></i>
                        <p>${searchTerm || activeFilter !== 'all' ? '没有找到匹配的工单' : '暂无维修工单,请提交第一个维修单'}</p>
                    </div>
                `;
                return;
            }

            ticketsList.innerHTML = filteredTickets.map(ticket => {
                const statusInfo = statusMap[ticket.status];
                const urgencyLabel = urgencyMap[ticket.urgency];
                const urgencyColor = ticket.urgency === 'critical' ? 'color: var(--danger); font-weight: bold;' : '';

                return `
                    <div class="ticket-item ${statusInfo.class}">
                        <div class="ticket-header">
                            <div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
                                <span class="ticket-number">${ticket.ticketNumber}</span>
                                <span class="ticket-title">${ticket.device}</span>
                            </div>
                            <div class="ticket-status ${statusInfo.statusClass}">${statusInfo.label}</div>
                        </div>
                        <div class="ticket-body">
                            <div><strong>问题:</strong>${ticket.problem}</div>
                            <div><strong>位置:</strong>${ticket.location}</div>
                            <div><strong>联系人:</strong>${ticket.contact} (${ticket.phone})</div>
                            <div><strong>紧急程度:</strong><span style="${urgencyColor}">${urgencyLabel}</span></div>
                        </div>
                        <div class="ticket-meta">
                            <span>单号:${ticket.ticketNumber}</span>
                            <span>提交:${ticket.createdAt}</span>
                            <span>更新:${ticket.updatedAt}</span>
                        </div>
                        <div class="ticket-actions">
                            <button class="btn btn-sm btn-warning" onclick="updateStatus(${ticket.id})">
                                <i class="fas fa-sync-alt"></i> 更新状态
                            </button>
                            <button class="btn btn-sm btn-success" onclick="completeTicket(${ticket.id})">
                                <i class="fas fa-check"></i> 完成
                            </button>
                            <button class="btn btn-sm btn-danger" onclick="deleteTicket(${ticket.id})">
                                <i class="fas fa-trash"></i> 删除
                            </button>
                            <div class="print-actions">
                                <button class="btn btn-sm btn-primary" onclick="printTicket(${ticket.id})">
                                    <i class="fas fa-print"></i> 打印
                                </button>
                                <button class="btn btn-sm btn-info" onclick="copyTicketInfo(${ticket.id})">
                                    <i class="fas fa-copy"></i> 复制发微信
                                </button>
                            </div>
                        </div>
                    </div>
                `;
            }).join('');
        }

        // 打印维修单 - 修复版本:使用新窗口打印,确保内容可见
        function printTicket(id) {
            const ticket = repairTickets.find(t => t.id === id);
            if (!ticket) return;

            const urgencyLabel = urgencyMap[ticket.urgency];
            const statusLabel = statusMap[ticket.status].label;

            // 创建完整的HTML文档用于打印
            const printHTML = `
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>维修单打印 - ${ticket.ticketNumber}</title>
    <style>
        body {
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
            margin: 20px;
            color: #333;
            line-height: 1.6;
        }
        .container {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            border: 2px solid #333;
            border-radius: 8px;
        }
        .header {
            text-align: center;
            margin-bottom: 20px;
            border-bottom: 2px solid #333;
            padding-bottom: 10px;
        }
        .header h1 {
            margin: 0;
            color: #2c3e50;
            font-size: 28px;
        }
        .header p {
            margin: 5px 0;
            color: #666;
            font-size: 14px;
        }
        .info-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 15px;
            margin: 20px 0;
        }
        .info-item {
            padding: 10px;
            background: #f8f9fa;
            border-radius: 4px;
            border-left: 3px solid #3498db;
        }
        .info-item.full-width {
            grid-column: 1 / -1;
        }
        .label {
            font-weight: bold;
            color: #2c3e50;
            display: block;
            margin-bottom: 5px;
            font-size: 14px;
        }
        .value {
            color: #333;
            font-size: 16px;
        }
        .status-bar {
            background: #ecf0f1;
            padding: 10px;
            border-radius: 4px;
            text-align: center;
            margin: 15px 0;
            font-weight: bold;
        }
        .urgency-high {
            color: #e74c3c;
            font-weight: bold;
        }
        .footer {
            margin-top: 30px;
            padding-top: 15px;
            border-top: 2px solid #333;
            text-align: center;
            font-size: 12px;
            color: #666;
        }
        .footer p {
            margin: 3px 0;
        }
        .print-time {
            text-align: right;
            font-size: 12px;
            color: #999;
            margin-bottom: 15px;
        }

        /* 打印优化 */
        @media print {
            body {
                margin: 0;
                background: white;
            }
            .container {
                border: 1px solid #000;
                box-shadow: none;
            }
            .no-print {
                display: none;
            }
        }

        /* 打印按钮(仅在打印预览时显示) */
        .print-actions {
            text-align: center;
            margin-top: 20px;
            padding: 15px;
            background: #f0f0f0;
            border-radius: 6px;
        }
        .print-btn {
            padding: 10px 20px;
            background: #3498db;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            margin: 0 10px;
        }
        .print-btn:hover {
            background: #2980b9;
        }
        .close-btn {
            background: #95a5a6;
        }
        .close-btn:hover {
            background: #7f8c8d;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="print-time">打印时间:${new Date().toLocaleString('zh-CN')}</div>

        <div class="header">
            <h1>维修服务单</h1>
            <p>单号:${ticket.ticketNumber}</p>
            <p>状态:${statusLabel}</p>
        </div>

        <div class="status-bar">
            紧急程度:${urgencyLabel} ${ticket.urgency === 'critical' ? ' ⚠️' : ''}
        </div>

        <div class="info-grid">
            <div class="info-item">
                <span class="label">设备名称</span>
                <span class="value">${ticket.device}</span>
            </div>
            <div class="info-item">
                <span class="label">设备位置</span>
                <span class="value">${ticket.location}</span>
            </div>
            <div class="info-item full-width">
                <span class="label">故障描述</span>
                <span class="value">${ticket.problem}</span>
            </div>
            <div class="info-item">
                <span class="label">联系人</span>
                <span class="value">${ticket.contact}</span>
            </div>
            <div class="info-item">
                <span class="label">联系电话</span>
                <span class="value">${ticket.phone}</span>
            </div>
            <div class="info-item">
                <span class="label">提交时间</span>
                <span class="value">${ticket.createdAt}</span>
            </div>
            <div class="info-item">
                <span class="label">最后更新</span>
                <span class="value">${ticket.updatedAt}</span>
            </div>
        </div>

        <div class="footer">
            <p>服务热线:138-0000-0000</p>
            <p>服务时间:工作日 9:00-18:00</p>
            <p>请保留此单据作为服务凭证</p>
        </div>

        <div class="print-actions no-print">
            <button class="print-btn" onclick="window.print()">🖨️ 打印</button>
            <button class="print-btn close-btn" onclick="window.close()">❌ 关闭</button>
        </div>
    </div>

    <script>
        // 自动打印(可选)
        // setTimeout(() => window.print(), 500);
    <\/script>
</body>
</html>
            `;

            // 打开新窗口并写入内容
            const printWindow = window.open('', '_blank', 'width=800,height=600');
            if (!printWindow) {
                showNotification('请允许浏览器弹出窗口', 'error');
                return;
            }

            printWindow.document.write(printHTML);
            printWindow.document.close();

            // 等待内容加载完成后打印
            printWindow.onload = function() {
                // 可选:自动打印(取消注释下面这行)
                // printWindow.print();
                showNotification('打印窗口已打开,请点击打印按钮', 'success');
            };

            // 如果浏览器阻止了弹出窗口
            setTimeout(() => {
                if (!printWindow.document.body || printWindow.document.body.innerHTML === '') {
                    showNotification('浏览器阻止了打印窗口,请手动允许弹出窗口', 'error');
                }
            }, 1000);
        }

        // 复制工单信息
        function copyTicketInfo(id) {
            const ticket = repairTickets.find(t => t.id === id);
            if (!ticket) return;

            const urgencyLabel = urgencyMap[ticket.urgency];
            const statusLabel = statusMap[ticket.status].label;

            const info = `【维修工单】${ticket.ticketNumber}
设备:${ticket.device}
问题:${ticket.problem}
位置:${ticket.location}
联系人:${ticket.contact}
电话:${ticket.phone}
紧急程度:${urgencyLabel}
状态:${statusLabel}
提交:${ticket.createdAt}

请尽快安排处理!`;

            copyInfoContent.textContent = info;
            copyModal.style.display = 'block';
            copySuccess.classList.remove('show');
        }

        // 复制到剪贴板
        function copyToClipboard() {
            const info = copyInfoContent.textContent;

            if (navigator.clipboard && navigator.clipboard.writeText) {
                navigator.clipboard.writeText(info).then(() => {
                    showCopySuccess();
                }).catch(() => {
                    fallbackCopy(info);
                });
            } else {
                fallbackCopy(info);
            }
        }

        // 降级复制方案
        function fallbackCopy(text) {
            const textArea = document.createElement('textarea');
            textArea.value = text;
            textArea.style.position = 'fixed';
            textArea.style.left = '-999999px';
            document.body.appendChild(textArea);
            textArea.select();

            try {
                document.execCommand('copy');
                showCopySuccess();
            } catch (err) {
                showNotification('复制失败,请手动复制', 'error');
            }

            document.body.removeChild(textArea);
        }

        // 显示复制成功
        function showCopySuccess() {
            copySuccess.classList.add('show');
            showNotification('信息已复制到剪贴板!', 'success');
            setTimeout(() => {
                copySuccess.classList.remove('show');
            }, 2000);
        }

        // 关闭复制模态框
        function closeCopyModal() {
            copyModal.style.display = 'none';
        }

        // 点击模态框外部关闭
        window.onclick = function(event) {
            if (event.target === copyModal) {
                closeCopyModal();
            }
        }

        // 更新状态
        function updateStatus(id) {
            const ticket = repairTickets.find(t => t.id === id);
            if (!ticket) return;

            const statusOrder = ['pending', 'progress', 'completed'];
            const currentIndex = statusOrder.indexOf(ticket.status);
            const nextIndex = (currentIndex + 1) % statusOrder.length;
            const newStatus = statusOrder[nextIndex];

            ticket.status = newStatus;
            ticket.updatedAt = new Date().toLocaleString('zh-CN');

            saveToLocalStorage();
            renderTickets();
            updateStats();
            showNotification(`工单 ${ticket.ticketNumber} 状态更新为:${statusMap[newStatus].label}`, 'success');
        }

        // 标记完成
        function completeTicket(id) {
            const ticket = repairTickets.find(t => t.id === id);
            if (!ticket) return;

            ticket.status = 'completed';
            ticket.updatedAt = new Date().toLocaleString('zh-CN');

            saveToLocalStorage();
            renderTickets();
            updateStats();
            showNotification(`工单 ${ticket.ticketNumber} 已完成!`, 'success');
        }

        // 删除工单
        function deleteTicket(id) {
            const ticket = repairTickets.find(t => t.id === id);
            if (!ticket) return;

            if (confirm(`确定要删除维修单 ${ticket.ticketNumber} 吗?此操作不可恢复。`)) {
                repairTickets = repairTickets.filter(t => t.id !== id);
                saveToLocalStorage();
                renderTickets();
                updateStats();
                showNotification(`工单 ${ticket.ticketNumber} 已删除`, 'error');
            }
        }

        // 更新统计
        function updateStats() {
            document.getElementById('total-count').textContent = repairTickets.length;
            document.getElementById('pending-count').textContent =
                repairTickets.filter(t => t.status === 'pending').length;
            document.getElementById('progress-count').textContent =
                repairTickets.filter(t => t.status === 'progress').length;
        }

        // 更新数据列表
        function updateDatalists() {
            const deviceDatalist = document.getElementById('device-list');
            const problemDatalist = document.getElementById('problem-list');

            deviceDatalist.innerHTML = '';
            problemDatalist.innerHTML = '';

            deviceHistory.forEach(device => {
                const option = document.createElement('option');
                option.value = device;
                option.textContent = device;
                deviceDatalist.appendChild(option);
            });

            problemHistory.forEach(problem => {
                const option = document.createElement('option');
                option.value = problem;
                option.textContent = problem;
                problemDatalist.appendChild(option);
            });

            updateHistoryDisplay('device');
            updateHistoryDisplay('problem');
        }

        // 更新历史记录显示
        function updateHistoryDisplay(type) {
            const container = document.getElementById(`${type}-history-list`);
            const history = type === 'device' ? deviceHistory : problemHistory;

            if (!container) return;

            if (history.length === 0) {
                container.innerHTML = '<div style="padding: 8px; color: var(--gray);">暂无历史记录</div>';
                return;
            }

            const usageCount = {};
            repairTickets.forEach(ticket => {
                const value = ticket[type];
                if (value && value !== '未指定设备' && value !== '未描述故障') {
                    usageCount[value] = (usageCount[value] || 0) + 1;
                }
            });

            container.innerHTML = history.map(item => `
                <div class="history-item" onclick="selectHistory('${type}', '${item}')">
                    ${item}
                    <span class="count">${usageCount[item] || 0}次</span>
                </div>
            `).join('');
        }

        // 切换历史记录显示
        function toggleHistory(type) {
            const container = document.getElementById(`${type}-history-list`);
            container.classList.toggle('show');
        }

        // 选择历史记录
        function selectHistory(type, value) {
            document.getElementById(type).value = value;
            document.getElementById(`${type}-history-list`).classList.remove('show');
        }

        // 清空输入
        function clearInput(id) {
            document.getElementById(id).value = '';
            document.getElementById(id).focus();
        }

        // 清空历史记录
        function clearHistory(type) {
            if (confirm(`确定要清空${type === 'device' ? '设备' : '故障'}的历史记录吗?`)) {
                if (type === 'device') {
                    deviceHistory = [];
                    localStorage.setItem('deviceHistory', JSON.stringify(deviceHistory));
                } else {
                    problemHistory = [];
                    localStorage.setItem('problemHistory', JSON.stringify(problemHistory));
                }
                updateDatalists();
                showNotification('历史记录已清空', 'error');
            }
        }

        // 保存到本地存储
        function saveToLocalStorage() {
            localStorage.setItem('repairTickets', JSON.stringify(repairTickets));
        }

        // 显示通知
        function showNotification(message, type = 'success') {
            notificationText.textContent = message;
            notification.className = `notification ${type}`;
            notification.classList.add('show');

            setTimeout(() => {
                notification.classList.remove('show');
            }, 3000);
        }

        // 添加一些示例数据(首次加载时)
        if (repairTickets.length === 0) {
            setTimeout(() => {
                repairTickets = [
                    {
                        id: Date.now() - 10000,
                        ticketNumber: 'AB12',
                        device: '服务器主机',
                        problem: '无法启动,电源指示灯不亮',
                        contact: '张经理',
                        phone: '13800138000',
                        location: '数据中心A区',
                        urgency: 'critical',
                        status: 'progress',
                        createdAt: new Date(Date.now() - 86400000).toLocaleString('zh-CN'),
                        updatedAt: new Date().toLocaleString('zh-CN')
                    },
                    {
                        id: Date.now() - 20000,
                        ticketNumber: 'CD34',
                        device: '打印机',
                        problem: '打印输出有条纹,色彩不正常',
                        contact: '李助理',
                        phone: '13900139000',
                        location: '办公室B区',
                        urgency: 'medium',
                        status: 'pending',
                        createdAt: new Date(Date.now() - 172800000).toLocaleString('zh-CN'),
                        updatedAt: new Date(Date.now() - 172800000).toLocaleString('zh-CN')
                    }
                ];

                deviceHistory = ['服务器主机', '打印机', '显示器', '键盘', '鼠标'];
                problemHistory = ['无法启动', '打印异常', '显示问题', '连接失败', '噪音过大'];

                saveToLocalStorage();
                localStorage.setItem('deviceHistory', JSON.stringify(deviceHistory));
                localStorage.setItem('problemHistory', JSON.stringify(problemHistory));

                renderTickets();
                updateStats();
                updateDatalists();
                showNotification('已加载示例数据和历史记录', 'success');
            }, 1000);
        }
    </script>
</body>
</html>