Help
RSS
API
Feed
Maltego
Contact
Domain > www.findseat.cn
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2025-08-12
112.47.51.147
(
ClassC
)
2025-10-23
112.47.51.148
(
ClassC
)
Port 80
HTTP/1.1 301 Moved PermanentlyDate: Thu, 23 Oct 2025 14:32:15 GMTContent-Type: text/htmlContent-Length: 166Connection: keep-aliveServer: openrestyLocation: https://www.findseat.cn/X-Ser: i47830_c8442, i43604_c26757X-Cache: MISS from i43604_c26757(cloudsvr) html>head>title>301 Moved Permanently/title>/head>body>center>h1>301 Moved Permanently/h1>/center>hr>center>openresty/center>/body>/html>
Port 443
HTTP/1.1 200 OKDate: Thu, 23 Oct 2025 14:32:17 GMTContent-Type: text/html; charsetUTF-8Content-Length: 170631Connection: keep-aliveServer: openrestyVary: Accept-EncodingX-Powered-By: ExpressAccept-Ranges: bytesCache-Control: public, max-age0Last-Modified: Thu, 09 Oct 2025 07:05:30 GMTETag: W/29a87-199c7ca0690x-user-response: trueCache-Control: public, max-age0X-Ser: i47830_c8442, i43604_c26757X-Cache: MISS from i43604_c26757(cloudsvr) !DOCTYPE html>html langzh-CN>head> meta charsetUTF-8> meta nameviewport contentwidthdevice-width, initial-scale1.0> title>宴会座位管理系统/title> link relstylesheet hrefhttps://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css> script srchttps://cdn.jsdelivr.net/npm/chart.js>/script> script srchttps://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js>/script> script srchttps://cdn.jsdelivr.net/npm/leancloud-storage@4.13.2/dist/av-min.js>/script> style> /* 登录页面样式 - 左右布局 */ .login-container { display: flex; min-height: 100vh; background: linear-gradient(135deg, #fdf6f8, #f8f4ff); } .login-left { flex: 1; display: flex; justify-content: center; align-items: center; padding: 40px; } .login-right { flex: 1; padding: 40px; background: #f9f9f9; overflow-y: auto; } .login-box { background: white; padding: 40px; border-radius: 15px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); width: 400px; max-width: 100%; text-align: center; } .login-box h1 { color: #5e35b1; margin-bottom: 30px; font-size: 2rem; } .login-form input { width: 100%; padding: 14px; margin-bottom: 15px; border: 2px solid #d1c4e9; border-radius: 10px; font-size: 1.1rem; transition: all 0.3s; } .login-form input:focus { border-color: #7e57c2; outline: none; box-shadow: 0 0 0 3px rgba(126, 87, 194, 0.2); } .login-btn { background: linear-gradient(90deg, #7e57c2, #5e35b1); color: white; border: none; border-radius: 10px; padding: 14px; font-size: 1.1rem; font-weight: bold; cursor: pointer; width: 100%; transition: all 0.3s; margin-top: 10px; } .login-btn:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(126, 87, 194, 0.4); } .login-links { margin-top: 20px; display: flex; justify-content: space-between; } .login-links a { color: #7e57b1; text-decoration: none; font-size: 0.9rem; } .login-links a:hover { text-decoration: underline; } .login-error { color: #f44336; margin-top: 10px; display: none; } /* 验证页面样式 */ .verification-container { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: linear-gradient(135deg, #fdf6f8, #f8f4ff); padding: 20px; } .verification-box { background: white; padding: 40px; border-radius: 15px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); width: 400px; max-width: 90%; text-align: center; } .verification-logo { text-align: center; margin-bottom: 30px; } .verification-logo h1 { color: #5e35b1; font-size: 2rem; margin-bottom: 10px; } .verification-logo p { color: #666; font-size: 1rem; } .verification-form { margin-bottom: 25px; } .form-group { margin-bottom: 20px; text-align: left; } .form-group label { display: block; margin-bottom: 8px; font-weight: 600; color: #4527a0; } .form-group input { width: 100%; padding: 14px; border: 2px solid #d1c4e9; border-radius: 10px; font-size: 1.1rem; transition: all 0.3s; } .form-group input:focus { border-color: #7e57c2; outline: none; box-shadow: 0 0 0 3px rgba(126, 87, 194, 0.2); } .verification-btn { background: linear-gradient(90deg, #7e57c2, #5e35b1); color: white; border: none; border-radius: 10px; padding: 14px; font-size: 1.1rem; font-weight: bold; cursor: pointer; width: 100%; transition: all 0.3s; margin-top: 10px; } .verification-btn:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(126, 87, 194, 0.4); } .verification-error { color: #f44336; margin-top: 10px; display: none; text-align: center; } .verification-links { margin-top: 20px; text-align: center; } .verification-links a { color: #7e57c2; text-decoration: none; font-size: 0.9rem; } .verification-links a:hover { text-decoration: underline; } /* 活动管理页面样式 */ .events-container { max-width: 1200px; margin: 0 auto; padding: 20px; } .events-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; flex-wrap: wrap; gap: 15px; } .events-header h1 { color: #4527a0; margin: 0; } .user-info { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } .user-info span { font-weight: 500; } .validity-info { background: #e8f5e9; padding: 10px 15px; border-radius: 8px; border-left: 4px solid #4CAF50; display: flex; align-items: center; gap: 10px; } .validity-days { font-weight: bold; color: #1b5e20; } .logout-btn { background: #f44336; color: white; border: none; border-radius: 8px; padding: 8px 15px; cursor: pointer; transition: all 0.3s; } .logout-btn:hover { background: #d32f2f; } .events-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 25px; } .event-card { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12); transition: all 0.3s ease; cursor: pointer; border: 2px solid #e1bee7; } .event-card:hover { transform: translateY(-8px); box-shadow: 0 15px 30px rgba(0, 0, 0, 0.18); } .event-title { font-size: 1.5rem; color: #4527a0; margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center; } .event-info { color: #666; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; } .event-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } .event-btn { padding: 8px 15px; border: none; border-radius: 8px; cursor: pointer; font-size: 0.9rem; transition: all 0.3s; } .edit-btn { background: #7e57c2; color: white; } .edit-btn:hover { background: #5e35b1; } .delete-btn { background: #f44336; color: white; } .delete-btn:hover { background: #d32f2f; } .add-event-btn { background: linear-gradient(90deg, #4caf50, #2e7d32); color: white; border: none; border-radius: 12px; padding: 14px 25px; font-size: 1.1rem; font-weight: bold; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; gap: 10px; margin-bottom: 30px; } .add-event-btn:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(76, 175, 80, 0.4); } /* 系统使用说明样式 */ .instructions-title { color: #4527a0; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 2px solid #d1c4e9; } .instructions-section { margin-bottom: 25px; } .instructions-section h3 { color: #5e35b1; margin-bottom: 10px; display: flex; align-items: center; gap: 10px; } .instructions-section p, .instructions-section ul { color: #555; line-height: 1.6; } .instructions-section ul { padding-left: 20px; } .instructions-section li { margin-bottom: 8px; } .important-note { background: #fff8e1; padding: 15px; border-radius: 8px; margin-top: 20px; border-left: 4px solid #ff9800; } .important-note h4 { color: #ef6c00; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; } /* 原系统样式 */ * { margin: 0; padding: 0; box-sizing: border-box; font-family: Segoe UI, Microsoft YaHei, sans-serif; } body { background: linear-gradient(135deg, #fdf6f8, #f8f4ff); min-height: 100vh; padding: 20px; color: #333; overflow-x: hidden; position: relative; padding-bottom: 120px; } .container { max-width: 1800px; margin: 0 auto; } header { text-align: center; padding: 25px; background: linear-gradient(90deg, #9c27b0, #673ab7); color: white; border-radius: 15px; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2); margin-bottom: 30px; position: relative; overflow: hidden; } header::before { content: ; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: url(data:image/svg+xml;utf8,svg xmlnshttp://www.w3.org/2000/svg viewBox0 0 100 100 preserveAspectRationone>path dM0,0 L100,0 L100,100 Z fillrgba(255,255,255,0.1)/>/svg>); background-size: cover; } header h1 { font-size: 2.8rem; margin-bottom: 10px; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); position: relative; } header p { font-size: 1.3rem; opacity: 0.92; letter-spacing: 1px; position: relative; } .system-container { display: flex; flex-wrap: wrap; gap: 30px; margin-bottom: 30px; } .layout-section { flex: 1; min-width: 300px; background: white; border-radius: 15px; padding: 25px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); transition: all 0.3s ease; position: relative; overflow: hidden; } .layout-section::before { content: ; position: absolute; top: 0; left: 0; right: 0; height: 6px; background: linear-gradient(90deg, #9c27b0, #673ab7); } .section-title { font-size: 2rem; margin-bottom: 25px; color: #4527a0; padding-bottom: 12px; border-bottom: 3px solid #7e57c2; display: flex; align-items: center; gap: 15px; } .section-title i { color: #7e57c2; background: #f3e5f5; width: 50px; height: 50px; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px rgba(156, 39, 176, 0.25); } .banquet-hall { position: relative; width: 100%; height: 1100px; background-color: #f9f5ff; border: 3px solid #d1c4e9; border-radius: 12px; overflow: hidden; box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.1); } .stage { position: absolute; width: 380px; height: 100px; background: linear-gradient(90deg, #ff5722, #e64a19); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 1.6rem; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); z-index: 10; text-shadow: 0 2px 3px rgba(0, 0, 0, 0.3); cursor: move; user-select: none; } .entrance { position: absolute; width: 180px; height: 60px; background: linear-gradient(90deg, #4caf50, #2e7d32); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 1.2rem; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); z-index: 10; text-shadow: 0 2px 3px rgba(0, 0, 0, 0.3); cursor: move; user-select: none; } .exit { position: absolute; width: 180px; height: 60px; background: linear-gradient(90deg, #4caf50, #2e7d32); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 1.2rem; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); z-index: 10; text-shadow: 0 2px 3px rgba(0, 0, 0, 0.3); cursor: move; user-select: none; } .aisle { position: absolute; width: 80px; background: linear-gradient(90deg, #ede7f6, #d1c4e9); z-index: 5; box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.05); cursor: move; user-select: none; } .table-grid { position: absolute; top: 150px; bottom: 130px; left: 0; right: 0; display: grid; grid-template-columns: repeat(10, 1fr); gap: 15px; padding: 20px; } .table { width: 50px; height: 50px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 0.7rem; transition: all 0.3s ease; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.2); position: relative; overflow: hidden; text-align: center; padding: 8px; line-height: 1.2; z-index: 10; cursor: move; user-select: none; } .table:hover { transform: scale(1.08); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); z-index: 20; } .table.main-table { background: radial-gradient(circle, #ff9800, #f57c00); width: 65px; height: 65px; font-size: 0.8rem; z-index: 15; } .table.groom-family { background: radial-gradient(circle, #42a5f5, #1976d2); } .table.groom-friends { background: radial-gradient(circle, #26a69a, #00897b); } .table.bride-family { background: radial-gradient(circle, #ab47bc, #7b1fa2); } .table.bride-friends { background: radial-gradient(circle, #ef5350, #d32f2f); } .table.area5 { background: radial-gradient(circle, #ffeb3b, #fbc02d); } .table.area6 { background: radial-gradient(circle, #9e9e9e, #616161); } .table i { position: absolute; bottom: 4px; right: 4px; font-size: 0.7rem; } .table .fa-check { color: #4CAF50; text-shadow: 0 1px 2px rgba(0,0,0,0.3); } .table .fa-question { color: #FF9800; text-shadow: 0 1px 2px rgba(0,0,0,0.3); } .search-section { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); margin-top: 30px; transition: all 0.3s ease; position: relative; overflow: hidden; } .search-section::before { content: ; position: absolute; top: 0; left: 0; right: 0; height: 6px; background: linear-gradient(90deg, #4caf50, #2e7d32); } .search-container { display: flex; gap: 20px; margin-bottom: 25px; } .search-container input { flex: 1; padding: 16px 25px; border: 2px solid #d1c4e9; border-radius: 12px; font-size: 1.2rem; transition: all 0.3s; background: #f9f5ff; } .search-container input:focus { border-color: #7e57c2; outline: none; box-shadow: 0 0 0 4px rgba(126, 87, 194, 0.2); background: white; } .search-container button { background: linear-gradient(90deg, #7e57c2, #5e35b1); color: white; border: none; border-radius: 12px; padding: 0 40px; font-size: 1.2rem; font-weight: bold; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 15px rgba(126, 87, 194, 0.3); display: flex; align-items: center; gap: 10px; } .search-container button:hover { transform: translateY(-3px); box-shadow: 0 10px 25px rgba(126, 87, 194, 0.4); } .result-container { background: linear-gradient(to bottom, #f3e5f5, #e8eaf6); border-radius: 12px; padding: 25px; margin-top: 25px; display: none; border: 2px solid #d1c4e9; box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.05); } .result-container.active { display: block; animation: fadeIn 0.6s ease; } @keyframes fadeIn { from { opacity: 0; transform: translateY(-15px); } to { opacity: 1; transform: translateY(0); } } .result-title { font-size: 1.6rem; color: #4527a0; margin-bottom: 20px; display: flex; align-items: center; gap: 15px; } .result-details { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-bottom: 25px; } .detail-item { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); border: 1px solid #e1bee7; } .detail-label { font-weight: bold; color: #7e57c2; margin-bottom: 8px; font-size: 1.1rem; } .detail-value { font-size: 1.3rem; color: #311b92; font-weight: 600; } .action-buttons { display: flex; gap: 20px; justify-content: center; margin-top: 20px; } .action-buttons button { padding: 15px 45px; border: none; border-radius: 12px; font-size: 1.2rem; font-weight: bold; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; gap: 10px; box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); } .confirm-btn { background: linear-gradient(90deg, #4caf50, #2e7d32); color: white; } .confirm-btn:hover { transform: translateY(-3px); box-shadow: 0 10px 25px rgba(76, 175, 80, 0.4); } .cancel-btn { background: linear-gradient(90deg, #f44336, #d32f2f); color: white; } .cancel-btn:hover { transform: translateY(-3px); box-shadow: 0 10px 25px rgba(244, 67, 54, 0.4); } .admin-section { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); margin-top: 30px; transition: all 0.3s ease; display: none; position: relative; overflow: hidden; } .admin-section.active { display: block; animation: fadeIn 0.6s ease; } .admin-section::before { content: ; position: absolute; top: 0; left: 0; right: 0; height: 6px; background: linear-gradient(90deg, #ff9800, #f57c00); } .admin-controls { display: flex; justify-content: space-between; margin-bottom: 25px; flex-wrap: wrap; gap: 15px; } .admin-btn { background: linear-gradient(90deg, #7e57c2, #5e35b1); color: white; border: none; border-radius: 12px; padding: 14px 35px; font-size: 1.2rem; font-weight: bold; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 15px rgba(126, 87, 194, 0.3); display: flex; align-items: center; gap: 10px; } .admin-btn:hover { transform: translateY(-3px); box-shadow: 0 10px 25px rgba(126, 87, 194, 0.4); } .qrcode-section { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); margin-top: 30px; position: relative; overflow: hidden; } .qrcode-section::before { content: ; position: absolute; top: 0; left: 0; right: 0; height: 6px; background: linear-gradient(90deg, #ff6b6b, #ff8e53); } .qrcode-controls { display: flex; align-items: center; gap: 20px; margin-bottom: 20px; } .qrcode-info { color: #666; font-size: 0.95rem; } .qrcode-container { text-align: center; padding: 20px; background: #f9f9f9; border-radius: 10px; border: 1px solid #e0e0e0; } #qrcodeImage { margin-bottom: 15px; } #qrcodeImage img { max-width: 250px; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } .qrcode-details { text-align: left; background: white; padding: 15px; border-radius: 8px; margin-bottom: 15px; border: 1px solid #eee; } .qrcode-details p { margin: 8px 0; font-size: 0.95rem; } .qrcode-status { margin-top: 15px; padding: 10px; border-radius: 8px; text-align: center; display: none; } .qrcode-status.success { background-color: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; } .qrcode-status.error { background-color: #ffebee; color: #d32f2f; border: 1px solid #ffcdd2; } .guest-table-container { max-height: 500px; overflow-y: auto; border-radius: 12px; border: 2px solid #d1c4e9; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08); margin-bottom: 30px; } .guest-table { width: 100%; border-collapse: collapse; min-width: 800px; } .guest-table th { background: linear-gradient(90deg, #7e57c2, #5e35b1); color: white; padding: 18px; text-align: left; position: sticky; top: 0; font-size: 1.1rem; } .guest-table td { padding: 16px 18px; border-bottom: 1px solid #e1bee7; font-size: 1.05rem; } .guest-table tr:nth-child(even) { background-color: #f9f5ff; } .guest-table tr:hover { background-color: #f3e5f5; } .attendance-status { display: inline-block; padding: 7px 15px; border-radius: 20px; font-size: 1rem; font-weight: bold; min-width: 90px; text-align: center; } .attendance-yes { background-color: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; } .attendance-no { background-color: #ffebee; color: #d32f2f; border: 1px solid #ffcdd2; } .status-toggle { position: relative; display: inline-block; width: 55px; height: 28px; } .status-toggle input { opacity: 0; width: 0; height: 0; } .toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 28px; } .toggle-slider:before { position: absolute; content: ; height: 22px; width: 22px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; transform: translateX(0); } input:checked + .toggle-slider { background-color: #4caf50; } input:checked + .toggle-slider:before { transform: translateX(27px); } .stats-container { display: grid; grid-template-columns: repeat(3, 1fr); gap: 25px; margin-top: 35px; } .stat-card { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12); text-align: center; transition: all 0.4s ease; border: 2px solid #e1bee7; } .stat-card:hover { transform: translateY(-8px); box-shadow: 0 15px 30px rgba(0, 0, 0, 0.18); } .stat-value { font-size: 2.8rem; font-weight: bold; margin: 15px 0; text-shadow: 0 3px 6px rgba(0, 0, 0, 0.1); } .stat-label { font-size: 1.2rem; color: #5e35b1; font-weight: 600; } .stat-total .stat-value { color: #7e57c2; } .stat-confirmed .stat-value { color: #4caf50; } .stat-pending .stat-value { color: #ff9800; } .legend { display: flex; justify-content: center; gap: 25px; margin-top: 25px; flex-wrap: wrap; } .legend-item { display: flex; align-items: center; gap: 10px; font-size: 1.1rem; font-weight: 500; background: #f3e5f5; padding: 10px 20px; border-radius: 30px; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08); } .legend-color { width: 25px; height: 25px; border-radius: 50%; } .legend-main { background: radial-gradient(circle, #ff9800, #f57c00); } .legend-groom-family { background: radial-gradient(circle, #42a5f5, #1976d2); } .legend-groom-friends { background: radial-gradient(circle, #26a69a, #00897b); } .legend-bride-family { background: radial-gradient(circle, #ab47bc, #7b1fa2); } .legend-bride-friends { background: radial-gradient(circle, #ef5350, #d32f2f); } .legend-area5 { background: radial-gradient(circle, #ffeb3b, #fbc02d); } .legend-area6 { background: radial-gradient(circle, #9e9e9e, #616161); } .admin-actions { display: flex; gap: 12px; } .edit-btn { background: linear-gradient(90deg, #29b6f6, #0288d1); color: white; border: none; border-radius: 8px; padding: 8px 18px; font-size: 0.95rem; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; gap: 5px; } .edit-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(41, 182, 246, 0.3); } .delete-btn { background: linear-gradient(90deg, #f44336, #d32f2f); color: white; border: none; border-radius: 8px; padding: 8px 18px; font-size: 0.95rem; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; gap: 5px; } .delete-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(244, 67, 54, 0.3); } .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); z-index: 1000; align-items: center; justify-content: center; } .modal-content { background: white; border-radius: 15px; width: 500px; max-width: 90%; box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3); overflow: hidden; } .modal-header { background: linear-gradient(90deg, #7e57c2, #5e35b1); color: white; padding: 20px; font-size: 1.4rem; font-weight: bold; } .modal-body { padding: 25px; } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 8px; font-weight: 600; color: #4527a0; } .form-group input, .form-group select { width: 100%; padding: 14px; border: 2px solid #d1c4e9; border-radius: 10px; font-size: 1.1rem; } .form-group input:focus, .form-group select:focus { border-color: #7e57c2; outline: none; box-shadow: 0 0 0 3px rgba(126, 87, 194, 0.2); } .modal-footer { padding: 20px; display: flex; justify-content: flex-end; gap: 15px; border-top: 1px solid #eee; } .modal-btn { padding: 12px 30px; border: none; border-radius: 10px; font-size: 1.1rem; font-weight: bold; cursor: pointer; transition: all 0.3s; } .btn-primary { background: linear-gradient(90deg, #7e57c2, #5e35b1); color: white; } .btn-secondary { background: #e0e0e0; color: #333; } @keyframes bounce { 0%, 100% { transform: translateY(0); } 25% { transform: translateY(-15px); } 50% { transform: translateY(0); } 75% { transform: translateY(-10px); } } .bounce { animation: bounce 0.8s ease; } .status-indicator { display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 8px; } .status-confirmed { background-color: #4CAF50; } .status-pending { background-color: #FF9800; } .front-desk-notice { background: #e3f2fd; border-left: 4px solid #2196F3; padding: 15px; margin-top: 20px; border-radius: 0 8px 8px 0; } .chart-container { background: white; border-radius: 15px; padding: 20px; margin-top: 30px; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12); } .sync-notification { position: fixed; top: 20px; right: 20px; background: #4caf50; color: white; padding: 15px 25px; border-radius: 10px; box-shadow: 0 5px 15px rgba(0,0,0,0.2); z-index: 2000; display: flex; align-items: center; gap: 10px; font-weight: 500; display: none; } .dragging { z-index: 1000; opacity: 0.9; box-shadow: 0 0 20px rgba(0,0,0,0.4); } .data-status { padding: 10px 15px; margin-top: 15px; border-radius: 8px; text-align: center; font-weight: 500; display: none; } .data-status.saved { background-color: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; } .data-status.error { background-color: #ffebee; color: #d32f2f; border: 1px solid #ffcdd2; } .import-container { display: flex; align-items: center; gap: 15px; margin-top: 20px; } .import-info { background: #e3f2fd; padding: 10px 15px; border-radius: 8px; border-left: 3px solid #2196F3; flex-grow: 1; } .progress-bar { height: 10px; background-color: #e0e0e0; border-radius: 5px; margin-top: 10px; overflow: hidden; } .progress { height: 100%; background: linear-gradient(90deg, #4caf50, #2e7d32); width: 0%; transition: width 0.3s ease; } .debug-console { background: #2c3e50; color: #ecf0f1; padding: 15px; border-radius: 8px; margin-top: 20px; font-family: monospace; max-height: 200px; overflow-y: auto; } .debug-title { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .debug-content { white-space: pre-wrap; font-size: 0.9rem; } .layout-save-status { margin-top: 15px; } .guest-search { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); margin-bottom: 30px; position: relative; overflow: hidden; } .guest-search::before { content: ; position: absolute; top: 0; left: 0; right: 0; height: 6px; background: linear-gradient(90deg, #4caf50, #2e7d32); } .guest-result { background: linear-gradient(to bottom, #e8f5e9, #f1f8e9); border-radius: 12px; padding: 25px; margin-top: 25px; display: none; border: 2px solid #c8e6c9; box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.05); } .guest-result.active { display: block; animation: fadeIn 0.6s ease; } .guest-details { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-bottom: 25px; } .guest-item { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); border: 1px solid #c8e6c9; } .guest-label { font-weight: bold; color: #2e7d32; margin-bottom: 8px; font-size: 1.1rem; } .guest-value { font-size: 1.3rem; color: #1b5e20; font-weight: 600; } .status-toggle-container { display: flex; align-items: center; justify-content: center; gap: 15px; margin-top: 15px; } .status-text { font-weight: bold; font-size: 1.1rem; } .status-toggle-label { position: relative; display: inline-block; width: 60px; height: 34px; } .status-toggle-label input { opacity: 0; width: 0; height: 0; } .status-toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; } .status-toggle-slider:before { position: absolute; content: ; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .status-toggle-slider { background-color: #4CAF50; } input:checked + .status-toggle-slider:before { transform: translateX(26px); } .update-notice { background: #e3f2fd; border-left: 4px solid #2196F3; padding: 15px; margin-top: 20px; border-radius: 0 8px 8px 0; font-size: 0.95rem; } /* 互疏柳強丼惚 */ .table.highlight { box-shadow: 0 0 15px 5px #ffeb3b; animation: bounce 0.8s ease 3; z-index: 100; } .editable-info { background: #e3f2fd; padding: 20px; border-radius: 15px; margin-top: 20px; border: 2px solid #42a5f5; } .editable-info h3 { color: #1976d2; margin-bottom: 15px; display: flex; align-items: center; gap: 10px; } .visibility-controls { background: #fff8e1; padding: 20px; border-radius: 15px; margin-top: 20px; border: 2px solid #ffc107; } .visibility-controls h3 { color: #ff8f00; margin-bottom: 15px; display: flex; align-items: center; gap: 10px; } .visibility-item { display: flex; align-items: center; margin-bottom: 12px; padding: 10px; background: white; border-radius: 8px; border: 1px solid #e0e0e0; } .visibility-item label { margin-left: 10px; font-weight: 500; flex-grow: 1; } .toggle-container { position: relative; display: inline-block; width: 50px; height: 24px; } .toggle-container input { opacity: 0; width: 0; height: 0; } .toggle-slider-round { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 24px; } .toggle-slider-round:before { position: absolute; content: ; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .toggle-slider-round { background-color: #4CAF50; } input:checked + .toggle-slider-round:before { transform: translateX(26px); } .save-info-btn { background: linear-gradient(90deg, #4caf50, #2e7d32); color: white; border: none; border-radius: 8px; padding: 10px 20px; margin-top: 15px; cursor: pointer; transition: all 0.3s; font-weight: 500; display: flex; align-items: center; gap: 8px; } .save-info-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(76, 175, 80, 0.3); } .table-controls { background: #e8f5e9; padding: 20px; border-radius: 15px; margin-top: 20px; border: 2px solid #4CAF50; } .table-controls h3 { color: #2e7d32; margin-bottom: 15px; display: flex; align-items: center; gap: 10px; } .table-count-input { display: flex; gap: 15px; align-items: center; } .table-count-input input { width: 120px; padding: 12px; border: 2px solid #d1c4e9; border-radius: 10px; font-size: 1.1rem; } .area-config { background: #f5e1f7; padding: 20px; border-radius: 15px; margin-top: 20px; border: 2px solid #ab47bc; } .area-config h3 { color: #7b1fa2; margin-bottom: 15px; display: flex; align-items: center; gap: 10px; } .area-config-item { display: flex; align-items: center; margin-bottom: 15px; padding: 15px; background: white; border-radius: 10px; box-shadow: 0 3px 8px rgba(0,0,0,0.1); } .area-label { min-width: 120px; font-weight: bold; color: #7b1fa2; } .area-range { display: flex; align-items: center; gap: 10px; flex: 1; } .area-range input { width: 100px; padding: 10px; border: 2px solid #d1c4e9; border-radius: 8px; font-size: 1rem; } .area-color { width: 30px; height: 30px; border-radius: 50%; margin-left: 15px; } .area-main-color { background: radial-gradient(circle, #ff9800, #f57c00); } .area-groom-family-color { background: radial-gradient(circle, #42a5f5, #1976d2); } .area-groom-friends-color { background: radial-gradient(circle, #26a69a, #00897b); } .area-bride-family-color { background: radial-gradient(circle, #ab47bc, #7b1fa2); } .area-bride-friends-color { background: radial-gradient(circle, #ef5350, #d32f2f); } .area-area5-color { background: radial-gradient(circle, #ffeb3b, #fbc02d); } .area-area6-color { background: radial-gradient(circle, #9e9e9e, #616161); } .area-range-info { margin-left: 15px; font-size: 0.9rem; color: #7b1fa2; font-weight: 500; } .save-area-btn { background: linear-gradient(90deg, #7e57c2, #5e35b1); color: white; border: none; border-radius: 8px; padding: 10px 20px; margin-top: 15px; cursor: pointer; transition: all 0.3s; font-weight: 500; display: flex; align-items: center; gap: 8px; } .save-area-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(126, 87, 194, 0.3); } .footer { position: fixed; bottom: 0; left: 0; right: 0; background: #f8f9fa; padding: 12px 20px; text-align: center; border-top: 1px solid #eaeaea; z-index: 1001; display: flex; justify-content: center; align-items: center; gap: 20px; box-shadow: 0 -2px 10px rgba(0,0,0,0.1); } .footer a { color: #666; text-decoration: none; font-size: 0.9rem; display: flex; align-items: center; gap: 6px; transition: color 0.3s; } .footer a:hover { color: #5e35b1; } .download-link { display: inline-block; padding: 10px 15px; background: #e3f2fd; border-radius: 8px; color: #1976d2; text-decoration: none; margin-top: 10px; transition: all 0.3s; border: 1px solid #bbdefb; } .download-link:hover { background: #bbdefb; transform: translateY(-2px); } .import-guide { background: #fff3e0; padding: 15px; border-radius: 10px; margin-top: 15px; border-left: 4px solid #ff9800; } .import-guide h4 { color: #ef6c00; margin-bottom: 10px; display: flex; align-items: center; gap: 10px; } .import-guide ul { padding-left: 20px; } .import-guide li { margin-bottom: 8px; line-height: 1.5; } .back-to-events { position: absolute; top: 30px; left: 30px; background: linear-gradient(90deg, #7e57c2, #5e35b1); color: white; border: none; border-radius: 10px; padding: 12px 20px; font-size: 1rem; font-weight: bold; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; gap: 8px; z-index: 100; } .back-to-events:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(126, 87, 194, 0.4); } /* 新增:备案信息样式 */ .footer-beian { position: fixed; bottom: 0; left: 0; right: 0; background: #f8f9fa; padding: 12px 20px; text-align: center; border-top: 1px solid #eaeaea; z-index: 1001; display: flex; justify-content: center; align-items: center; gap: 20px; box-shadow: 0 -2px 10px rgba(0,0,0,0.1); } .footer-beian a { color: #666; text-decoration: none; font-size: 0.9rem; display: flex; align-items: center; gap: 6px; transition: color 0.3s; } .footer-beian a:hover { color: #5e35b1; } .beian-icon { width: 16px; height: 16px; margin-right: 5px; } /style>/head>body> !-- 登录页面 --> div idloginPage classlogin-container> div classlogin-left> div classlogin-box> h1>i classfas fa-heart>/i> 宴会管理系统/h1> div classlogin-form> input typetext idusername placeholder用户名> input typepassword idpassword placeholder密码> button classlogin-btn idloginBtn>登录/button> div classlogin-links> a href# idshowRegister>注册新账号/a> a href# idforgotPassword>忘记密码?/a> /div> div classlogin-error idloginError>/div> /div> div idregisterForm styledisplay: none;> input typetext idregUsername placeholder设置用户名> input typepassword idregPassword placeholder设置密码> input typepassword idregConfirmPassword placeholder确认密码> input typeemail idregEmail placeholder电子邮箱(可选)> button classlogin-btn idregisterBtn>注册/button> div classlogin-links> a href# idshowLogin>返回登录/a> /div> div classlogin-error idregisterError>/div> /div> /div> /div> div classlogin-right> h2 classinstructions-title>系统使用说明/h2> div classinstructions-section> h3>i classfas fa-user-tie>/i> 对于主办方:/h3> ol> li>strong>登录系统/strong>:使用账号密码登录Web管理后台。/li> li>strong>创建活动/strong>:点击新建活动,填写宴会基本信息(如新人姓名、日期、地点),有助于多活动时区分。/li> li>strong>设计布局/strong>:在布局编辑器中,拖拽生成宴会厅的舞台、餐桌(可编号),并保存布局。/li> li>strong>编辑信息/strong>:编辑宴会详细信息;编辑宾客就座区域;编辑宴会的总桌数。/li> li>strong>导入宾客/strong>:录入或导入宾客名单,并将其分配至已创建好的桌号。/li> li>strong>发布与分享/strong>:保存活动后,系统会生成该活动的专属小程序码。将此二维码印刷在邀请函或现场指引牌上。/li> /ol> /div> div classinstructions-section> h3>i classfas fa-users>/i> 对于宾客:/h3> ol> li>strong>扫码进入/strong>:使用微信扫描邀请函或现场指引牌上的小程序码。/li> li>strong>查询席位/strong>:在输入框内输入您的姓名,点击查询按钮,即可显示您的桌号、区域和出席状态。/li> li>strong>确认出席/strong>:如需确认或修改出席状态,在查询结果下方切换出席状态开关,点击更新状态按钮进行保存。/li> /ol> /div> div classinstructions-section> h3>i classfas fa-exclamation-circle>/i> 重要注意事项:/h3> ul> li>一个账号可管理多个活动,扫码可确保您进入正确的活动页面/li> li>通过扫描不同活动的小程序码进入,系统会自动识别并刷新页面数据/li> li>宾客端小程序隐藏了活动选择器,确保信息不会混淆/li> li>任何对席位或状态的修改都会实时同步到后台/li> /ul> /div> div classimportant-note> h4>i classfas fa-info-circle>/i> 请注意:/h4> p>新注册用户需要验证码才能激活账户。验证码和有效期由系统管理员提供。/p> p>如果您尚未获得验证码,请联系系统管理员获取。电子邮箱:1650375231@qq.com/p> /div> /div> /div> !-- 验证页面 --> div idverificationPage classverification-container styledisplay: none;> div classverification-box> div classverification-logo> h1>i classfas fa-heart>/i> 账户验证/h1> p>请输入验证码以激活您的账户/p> /div> div classverification-form> div classform-group> label forverificationUsername>用户名/label> input typetext idverificationUsername placeholder请输入您的用户名 readonly> /div> div classform-group> label forverificationCode>验证码/label> input typetext idverificationCode placeholder请输入6位数字验证码 maxlength6> /div> button classverification-btn idverifyBtn>验证并激活账户/button> div classverification-error idverificationError>/div> /div> div classverification-links> a href# idbackToLogin>返回登录页面/a> /div> /div> /div> !-- 活动管理页面 --> div ideventsPage classevents-container styledisplay: none;> div classevents-header> h1>i classfas fa-heart>/i> 我的宴会活动/h1> div classuser-info> span iduserName>用户/span> div classvalidity-info> i classfas fa-calendar-check>/i> span>有效期剩余: /span> span classvalidity-days idvalidityDays>-- 天/span> /div> button classlogout-btn idlogoutBtn>i classfas fa-sign-out-alt>/i> 退出/button> /div> /div> button classadd-event-btn idaddEventBtn> i classfas fa-plus>/i> 创建新活动 /button> div classevents-grid ideventsGrid> !-- 活动卡片将由JS动态生成 --> /div> /div> !-- 原系统编辑页面 --> div ideditorPage styledisplay: none;> button classback-to-events idbackToEventsBtn> i classfas fa-arrow-left>/i> 返回活动列表 /button> div classcontainer> header> h1>i classfas fa-heart>/i> span ideventTitle>宴会座位管理系统/span>/h1> p classwedding-info editable-text>张三 & 李四 , 2023年10月1日 , 国际大酒店宴会厅/p> /header> div classsync-notification idsyncNotification> i classfas fa-sync-alt>/i> span>正在同步数据,请稍候.../span> /div> div classsystem-container> div classlayout-section> h2 classsection-title>i classfas fa-map-marked-alt>/i> 宴会大厅布局编辑器/h2> div classbanquet-hall idlayoutEditor> div classstage idstage styletop: 30px; left: 50%; transform: translateX(-50%);>宴会舞台/div> !-- 两个出入口 --> div classentrance identrance1 stylebottom: 30px; left: 30%;>出入口1/div> div classexit identrance2 stylebottom: 30px; right: 30%;>出入口2/div> div classaisle idaisle styletop: 150px; left: 50%; height: 500px; transform: translateX(-50%);>/div> !-- 100桌布局 --> div classtable-grid idtableGrid> !-- 桌子由JS动态生成 --> /div> /div> div classlegend> div classlegend-item> div classlegend-color legend-main>/div> span classlegend-main-text>主桌/span> /div> div classlegend-item> div classlegend-color legend-groom-family>/div> span classlegend-groom-family-text>男方亲戚/span> /div> div classlegend-item> div classlegend-color legend-groom-friends>/div> span classlegend-groom-friends-text>男方朋友/span> /div> div classlegend-item> div classlegend-color legend-bride-family>/div> span classlegend-bride-family-text>女方亲戚/span> /div> div classlegend-item> div classlegend-color legend-bride-friends>/div> span classlegend-bride-friends-text>女方朋友/span> /div> div classlegend-item> div classlegend-color legend-area5>/div> span classlegend-area5-text>公司同事/span> /div> div classlegend-item> div classlegend-color legend-area6>/div> span classlegend-area6-text>备桌/span> /div> /div> !-- 保存布局按钮 --> button classadmin-btn idsaveLayoutBtn stylemargin-top: 20px;> i classfas fa-save>/i> 保存大厅布局 /button> div idlayoutSaveStatus classdata-status layout-save-status>/div> /div> !-- 后台管理 --> div classlayout-section> h2 classsection-title>i classfas fa-tools>/i> 后台管理/h2> !-- 可编辑区域(宴会信息编辑) --> div classeditable-info> h3>i classfas fa-edit>/i> 宴会信息编辑/h3> div classform-group> label forweddingInfo>宴会信息/label> input typetext idweddingInfo classeditable-input placeholder例如:张三 & 李四 , 2023年10月1日 , 国际大酒店宴会厅> /div> div classform-group> label formainTableText>主桌文本/label> input typetext idmainTableText classlegend-text-input placeholder主桌> /div> div classform-group> label forgroomFamilyText>区域1文本/label> input typetext idgroomFamilyText classlegend-text-input placeholder男方亲戚> /div> div classform-group> label forgroomFriendsText>区域2文本/label> input typetext idgroomFriendsText classlegend-text-input placeholder男方朋友> /div> div classform-group> label forbrideFamilyText>区域3文本/label> input typetext idbrideFamilyText classlegend-text-input placeholder女方亲戚> /div> div classform-group> label forbrideFriendsText>区域4文本/label> input typetext idbrideFriendsText classlegend-text-input placeholder女方朋友> /div> div classform-group> label forarea5Text>区域5文本/label> input typetext idarea5Text classlegend-text-input placeholder公司同事> /div> div classform-group> label forarea6Text>区域6文本/label> input typetext idarea6Text classlegend-text-input placeholder备桌> /div> button classsave-info-btn idsaveInfoBtn> i classfas fa-save>/i> 保存信息 /button> /div> !-- 区域配置 --> div classarea-config> h3>i classfas fa-cogs>/i> 区域桌数范围配置/h3> div idareaConfigs> !-- 区域配置由JS动态生成 --> /div> button classsave-area-btn idsaveAreaConfigBtn> i classfas fa-save>/i> 保存区域配置 /button> div classimport-guide stylemargin-top: 15px;> h4>i classfas fa-info-circle>/i> 配置说明:/h4> ul> li>主桌固定为1号桌,无需配置范围/li> li>每个区域配置最小和最大桌号范围/li> li>范围不能重叠且必须覆盖所有桌号(除主桌)/li> li>桌号范围必须为整数,最小桌号 ≤ 最大桌号/li> /ul> /div> /div> !-- 布局元素显示控制 --> div classvisibility-controls> h3>i classfas fa-eye>/i> 布局元素显示控制/h3> div classvisibility-item> div classtoggle-container> input typecheckbox idstageVisible checked> span classtoggle-slider-round>/span> /div> label forstageVisible>宴会舞台/label> /div> div classvisibility-item> div classtoggle-container> input typecheckbox identrance1Visible checked> span classtoggle-slider-round>/span> /div> label forentrance1Visible>出入口1/label> /div> div classvisibility-item> div classtoggle-container> input typecheckbox identrance2Visible checked> span classtoggle-slider-round>/span> /div> label forentrance2Visible>出入口2/label> /div> div classvisibility-item> div classtoggle-container> input typecheckbox idaisleVisible checked> span classtoggle-slider-round>/span> /div> label foraisleVisible>中间过道/label> /div> /div> !-- 桌数控制 --> div classtable-controls> h3>i classfas fa-chair>/i> 桌数控制/h3> div classform-group> label fortableCountInput>总桌数 (1-200)/label> div classtable-count-input> input typenumber idtableCountInput min1 max200 value100> button classadmin-btn idupdateTableCountBtn> i classfas fa-sync-alt>/i> 更新桌数 /button> /div> /div> /div> !-- 操作按钮组(添加宾客、刷新数据、导入Excel) --> div classadmin-controls stylemargin-top: 20px;> button classadmin-btn idaddGuestBtn>i classfas fa-user-plus>/i> 添加宾客/button> button classadmin-btn idrefreshBtn>i classfas fa-sync-alt>/i> 刷新数据/button> button classadmin-btn idimportExcelBtn>i classfas fa-file-import>/i> 导入Excel/button> /div> !-- 文件上传区域 --> div classimport-container> input typefile idexcelFileInput accept.xlsx, .xls styledisplay:none> div classimport-info idimportInfo> 未选择文件,请上传Excel格式的宾客数据 /div> /div> div stylemargin-top: 10px; margin-bottom: 20px;> a href# iddownloadTemplate classdownload-link> i classfas fa-download>/i> 下载示例Excel文件 /a> /div> div classprogress-bar idprogressBar> div classprogress idimportProgress>/div> /div> div classimport-guide> h4>i classfas fa-info-circle>/i> Excel导入指南/h4> ul> li>请使用下载的模板格式导入数据/li> li>确保包含以下列:strong>姓名/strong>、strong>桌号/strong>、strong>区域/strong>、strong>出席状态/strong>/li> li>出席状态填写:strong>是/strong> 或 strong>否/strong>/li> li>桌号可以是数字或文本格式/li> li>区域名称需与系统设置一致/li> /ul> /div> div classsearch-container stylemargin-bottom: 20px;> input typetext idadminSearch placeholder搜索宾客姓名> button idadminSearchBtn>i classfas fa-search>/i> 搜索/button> /div> div classguest-table-container> table classguest-table> thead> tr> th>姓名/th> th>桌号/th> th>区域/th> th>出席状态/th> th>操作/th> /tr> /thead> tbody idguestTableBody> !-- 宾客数据由JS动态生成 --> /tbody> /table> /div> div classdata-status iddataStatus>/div> div classstats-container> div classstat-card stat-total> i classfas fa-users fa-3x>/i> div classstat-value idstatTotal>0/div> div classstat-label>总宾客数/div> /div> div classstat-card stat-confirmed> i classfas fa-check-circle fa-3x>/i> div classstat-value idstatConfirmed>0/div> div classstat-label>已确认出席/div> /div> div classstat-card stat-pending> i classfas fa-hourglass-half fa-3x>/i> div classstat-value idstatPending>0/div> div classstat-label>待确认/div> /div> /div> div classchart-container> canvas idattendanceChart>/canvas> /div> div classqrcode-section> h3 classsection-title>i classfas fa-qrcode>/i> 小程序访问二维码/h3> div classqrcode-controls> button classadmin-btn idgenerateQrcodeBtn> i classfas fa-qrcode>/i> 生成小程序码 /button> div classqrcode-info> 生成后,宾客扫描此二维码可直接访问本次活动 /div> /div> div classqrcode-container idqrcodeContainer styledisplay: none;> div idqrcodeImage>/div> div classqrcode-details> p>strong>活动ID:/strong> span idqrcodeEventId>/span>/p> p>strong>活动名称:/strong> span idqrcodeEventName>/span>/p> p>strong>生成时间:/strong> span idqrcodeTime>/span>/p> /div> button classadmin-btn iddownloadQrcodeBtn stylemargin-top: 15px;> i classfas fa-download>/i> 下载二维码 /button> /div> div classqrcode-status idqrcodeStatus>/div> /div> div classdebug-console> div classdebug-title> h4>i classfas fa-bug>/i> 调试控制台/h4> button classadmin-btn idclearDebugBtn>清除日志/button> /div> div classdebug-content iddebugConsole> !-- 调试信息将在这里显示 --> /div> /div> /div> /div> /div> /div> !-- 添加/编辑宾客模态框 --> div classmodal idguestModal> div classmodal-content> div classmodal-header idmodalHeader>添加新宾客/div> div classmodal-body> div classform-group> label forguestNameInput>姓名/label> input typetext idguestModalNameInput placeholder请输入宾客姓名> /div> div classform-group> label forguestTableSelect>桌号/label> select idguestTableSelect> !-- 桌号选项由JS动态生成 --> /select> /div> div classform-group> label forguestAreaSelect>区域/label> select idguestAreaSelect> !-- 区域选项由JS动态生成 --> /select> /div> div classform-group> label forguestStatus>出席状态/label> select idguestStatus> option valuetrue>已确认/option> option valuefalse selected>未确认/option> /select> /div> /div> div classmodal-footer> button classmodal-btn btn-secondary idcancelModal>取消/button> button classmodal-btn btn-primary idsaveGuest>保存/button> /div> /div> /div> !-- 编辑活动名称模态框 --> div classmodal ideventNameModal> div classmodal-content> div classmodal-header>编辑活动名称/div> div classmodal-body> div classform-group> label foreventNameInput>活动名称/label> input typetext ideventNameInput placeholder请输入活动名称> /div> /div> div classmodal-footer> button classmodal-btn btn-secondary idcancelEventNameModal>取消/button> button classmodal-btn btn-primary idsaveEventName>保存/button> /div> /div> /div> !-- 备案信息 --> div classfooter-beian> a hrefhttps://beian.miit.gov.cn/ target_blank relnoopener> 苏ICP备2025191920号 /a> a hrefhttp://www.beian.gov.cn/portal/registerSystemInfo?recordcode32020502001770 target_blank relnoopener> img srchttps://app.findseat.cn/UXBweOU3rmihQ2mtc86J19zS5qzy8l2h/%E5%A4%87%E6%A1%88%E5%9B%BE%E6%A0%87.png alt公安备案 classbeian-icon> 苏公网安备32020502001770号 /a> /div> script> // LeanCloud配置 const LEANCLOUD_APP_ID OiF0tPJIzTr6a0Fx5KTwVSIx-gzGzoHsz; const LEANCLOUD_APP_KEY TVTqLx6hKXt6HbA9B9hkOnOm; const LEANCLOUD_SERVER_URL https://api.findseat.cn; // 全局变量 let currentUser null; let currentEvent null; let isAdmin true; let tableCount 100; // 布局数据 let layoutData { stage: { top: 30, left: 50, visible: true }, entrance1: { top: 90, left: 30, visible: true }, entrance2: { top: 90, left: 70, visible: true }, aisle: { top: 150, left: 50, height: 500, visible: true }, tables: , areaConfigs: null, tableCount: 100 }; // 区域颜色映射 let areaColorMap { 主桌: main-table, 男方亲戚: groom-family, 男方朋友: groom-friends, 女方亲戚: bride-family, 女方朋友: bride-friends, 公司同事: area5, 备桌: area6 }; // 区域配置 - 默认值 let areaConfigs { 主桌: { min: 1, max: 1 }, 男方亲戚: { min: 2, max: 20 }, 男方朋友: { min: 21, max: 40 }, 女方亲戚: { min: 41, max: 60 }, 女方朋友: { min: 61, max: 80 }, 公司同事: { min: 81, max: 95 }, 备桌: { min: 96, max: 100 } }; // 宴会信息 let banquetInfo { weddingInfo: 张三 & 李四 , 2023年10月1日 , 国际大酒店宴会厅, mainTableText: 主桌, groomFamilyText: 男方亲戚, groomFriendsText: 男方朋友, brideFamilyText: 女方亲戚, brideFriendsText: 女方朋友, area5Text: 公司同事, area6Text: 备桌 }; // 当前查询到的宾客对象 let currentGuest null; // 当前编辑的宾客对象(用于后台编辑) let currentEditingGuest null; // 初始化 function init() { // 初始化LeanCloud AV.init({ appId: LEANCLOUD_APP_ID, appKey: LEANCLOUD_APP_KEY, serverURL: LEANCLOUD_SERVER_URL }); // 检查是否已登录 currentUser AV.User.current(); if (currentUser) { // 已登录用户直接进入系统 showEventsPage(); loadEvents(); } else { // 未登录用户显示登录页面 showLoginPage(); } // 绑定事件 bindEvents(); } // 显示登录页面 function showLoginPage() { document.getElementById(loginPage).style.display flex; document.getElementById(verificationPage).style.display none; document.getElementById(eventsPage).style.display none; document.getElementById(editorPage).style.display none; } // 显示验证页面 function showVerificationPage(username) { document.getElementById(loginPage).style.display none; document.getElementById(verificationPage).style.display flex; document.getElementById(eventsPage).style.display none; document.getElementById(editorPage).style.display none; // 填充用户名 if (username) { document.getElementById(verificationUsername).value username; } } // 显示活动页面 function showEventsPage() { document.getElementById(loginPage).style.display none; document.getElementById(verificationPage).style.display none; document.getElementById(eventsPage).style.display block; document.getElementById(editorPage).style.display none; if (currentUser) { document.getElementById(userName).textContent currentUser.getUsername(); // 更新有效期信息 updateValidityInfo(currentUser.get(validityPeriod)); } } // 更新有效期信息 function updateValidityInfo(validityPeriod) { const validityDaysElement document.getElementById(validityDays); if (!validityPeriod) { validityDaysElement.textContent 0 天; validityDaysElement.style.color #f44336; return; } const validityDate new Date(validityPeriod); const today new Date(); const diffTime validityDate - today; const diffDays Math.ceil(diffTime / (1000 * 60 * 60 * 24)); if (diffDays 0) { validityDaysElement.textContent 已过期; validityDaysElement.style.color #f44336; } else { validityDaysElement.textContent `${diffDays} 天`; validityDaysElement.style.color diffDays 7 ? #ff9800 : #1b5e20; } } // 绑定事件 function bindEvents() { // 登录相关事件 document.getElementById(loginBtn).addEventListener(click, login); document.getElementById(registerBtn).addEventListener(click, register); document.getElementById(showRegister).addEventListener(click, showRegisterForm); document.getElementById(showLogin).addEventListener(click, showLoginForm); document.getElementById(logoutBtn).addEventListener(click, logout); document.getElementById(addEventBtn).addEventListener(click, addNewEvent); document.getElementById(backToEventsBtn).addEventListener(click, backToEvents); // 验证相关事件 document.getElementById(verifyBtn).addEventListener(click, verifyCode); document.getElementById(backToLogin).addEventListener(click, showLoginPage); // 编辑器相关事件 document.getElementById(saveLayoutBtn).addEventListener(click, saveLayout); document.getElementById(saveInfoBtn).addEventListener(click, saveBanquetInfo); document.getElementById(saveAreaConfigBtn).addEventListener(click, saveAreaConfigs); document.getElementById(updateTableCountBtn).addEventListener(click, updateTableCount); document.getElementById(addGuestBtn).addEventListener(click, showAddGuestModal); document.getElementById(refreshBtn).addEventListener(click, refreshGuestTable); document.getElementById(importExcelBtn).addEventListener(click, triggerExcelImport); document.getElementById(adminSearchBtn).addEventListener(click, searchGuest); document.getElementById(clearDebugBtn).addEventListener(click, clearDebugConsole); document.getElementById(cancelModal).addEventListener(click, closeGuestModal); document.getElementById(saveGuest).addEventListener(click, saveGuest); // 可见性控制 document.getElementById(stageVisible).addEventListener(change, toggleVisibility); document.getElementById(entrance1Visible).addEventListener(change, toggleVisibility); document.getElementById(entrance2Visible).addEventListener(change, toggleVisibility); document.getElementById(aisleVisible).addEventListener(change, toggleVisibility); // Excel导入 document.getElementById(excelFileInput).addEventListener(change, handleExcelImport); document.getElementById(downloadTemplate).addEventListener(click, downloadExcelTemplate); // 活动名称编辑 document.getElementById(cancelEventNameModal).addEventListener(click, closeEventNameModal); document.getElementById(saveEventName).addEventListener(click, saveEventName); // 绑定小程序码相关事件 document.getElementById(generateQrcodeBtn).addEventListener(click, generateMiniProgramQrcode); document.getElementById(downloadQrcodeBtn).addEventListener(click, downloadQrcode); } // 显示注册表单 function showRegisterForm(e) { e.preventDefault(); document.querySelector(.login-form).style.display none; document.getElementById(registerForm).style.display block; } // 显示登录表单 function showLoginForm(e) { e.preventDefault(); document.getElementById(registerForm).style.display none; document.querySelector(.login-form).style.display block; } // 登录功能 async function login() { const username document.getElementById(username).value; const password document.getElementById(password).value; const errorElement document.getElementById(loginError); if (!username || !password) { errorElement.textContent 请输入用户名和密码; errorElement.style.display block; return; } try { const user await AV.User.logIn(username, password); currentUser user; // 检查验证状态 const verificationCode user.get(verificationCode) || 0; const validityPeriod user.get(validityPeriod); // 如果验证码为0,表示是新用户,需要验证 if (verificationCode 0) { errorElement.textContent 新用户需要验证,请先进行验证; errorElement.style.display block; // 退出登录 AV.User.logOut(); currentUser null; // 跳转到验证页面 showVerificationPage(username); return; } // 检查有效期 if (!validityPeriod || new Date(validityPeriod) new Date()) { errorElement.textContent 账户已过期,请联系管理员; errorElement.style.display block; // 退出登录 AV.User.logOut(); currentUser null; // 跳转到验证页面 showVerificationPage(username); return; } // 登录成功,进入系统 showEventsPage(); loadEvents(); } catch (error) { errorElement.textContent 登录失败: + error.message; errorElement.style.display block; } } // 验证验证码 async function verifyCode() { const username document.getElementById(verificationUsername).value.trim(); const code document.getElementById(verificationCode).value.trim(); const errorElement document.getElementById(verificationError); if (!username || !code) { errorElement.textContent 请输入用户名和验证码; errorElement.style.display block; return; } if (!/^\d{6}$/.test(code)) { errorElement.textContent 验证码必须是6位数字; errorElement.style.display block; return; } try { // 查询用户 const query new AV.Query(AV.User); query.equalTo(username, username); const user await query.first(); if (!user) { errorElement.textContent 用户不存在; errorElement.style.display block; return; } // 检查验证码和有效期 const verificationCode user.get(verificationCode) || 0; const validityPeriod user.get(validityPeriod); if (verificationCode 0) { errorElement.textContent 账户未激活,请联系管理员获取验证码; errorElement.style.display block; return; } if (!validityPeriod || new Date(validityPeriod) new Date()) { errorElement.textContent 验证码已过期,请联系管理员; errorElement.style.display block; return; } if (verificationCode ! code) { errorElement.textContent 验证码不正确; errorElement.style.display block; return; } // 验证成功,激活账户 user.set(isActivated, true); await user.save(); // 显示成功消息 errorElement.textContent 验证成功,请登录; errorElement.style.color #4CAF50; errorElement.style.display block; // 2秒后跳转到登录页面 setTimeout(() > { showLoginPage(); document.getElementById(username).value username; }, 2000); } catch (error) { console.error(验证失败:, error); errorElement.textContent 验证失败: + error.message; errorElement.style.display block; } } // 注册功能 async function register() { const username document.getElementById(regUsername).value; const password document.getElementById(regPassword).value; const confirmPassword document.getElementById(regConfirmPassword).value; const email document.getElementById(regEmail).value; const errorElement document.getElementById(registerError); if (!username || !password) { errorElement.textContent 请输入用户名和密码; errorElement.style.display block; return; } if (password ! confirmPassword) { errorElement.textContent 两次输入的密码不一致; errorElement.style.display block; return; } try { const user new AV.User(); user.setUsername(username); user.setPassword(password); if (email) user.setEmail(email); // 设置默认验证信息 // 新用户验证码为0,表示需要验证 user.set(verificationCode, 0); // 新用户有效期为null,表示未激活 user.set(validityPeriod, null); // 新用户激活状态为false user.set(isActivated, false); await user.signUp(); // 注册成功后提示需要验证 errorElement.textContent 注册成功,请进行验证后登录; errorElement.style.color #4CAF50; errorElement.style.display block; // 跳转到验证页面 showVerificationPage(username); } catch (error) { errorElement.textContent 注册失败: + error.message; errorElement.style.display block; } } // 退出登录 function logout() { AV.User.logOut(); currentUser null; showLoginPage(); } // 加载用户的活动 async function loadEvents() { try { const query new AV.Query(Event); query.equalTo(owner, currentUser); query.descending(createdAt); const events await query.find(); displayEvents(events); } catch (error) { console.error(加载活动失败:, error); } } // 显示活动列表 function displayEvents(events) { const eventsGrid document.getElementById(eventsGrid); eventsGrid.innerHTML ; if (events.length 0) { eventsGrid.innerHTML p>您还没有创建任何活动,点击创建新活动按钮开始/p>; return; } events.forEach(event > { const eventCard document.createElement(div); eventCard.className event-card; eventCard.innerHTML ` div classevent-title> span>${event.get(name) || 未命名活动}/span> i classfas fa-edit edit-event-name data-id${event.id} title编辑活动名称>/i> /div> div classevent-info> i classfas fa-calendar>/i> span>创建时间: ${event.createdAt.toLocaleDateString()}/span> /div> div class event-info> i classfas fa-users>/i> span>宾客数量: ${event.get(guestCount) || 0}/span> /div> div classevent-actions> button classevent-btn edit-btn data-id${event.id}>编辑/button> button classevent-btn delete-btn data-id${event.id}>删除/button> /div> `; // 添加点击事件 eventCard.addEventListener(click, (e) > { if (!e.target.classList.contains(event-btn) && !e.target.classList.contains(edit-event-name)) { showEditorPage(event); } }); // 添加编辑按钮事件 const editBtn eventCard.querySelector(.edit-btn); editBtn.addEventListener(click, (e) > { e.stopPropagation(); showEditorPage(event); }); // 添加编辑活动名称按钮事件 const editNameBtn eventCard.querySelector(.edit-event-name); editNameBtn.addEventListener(click, (e) > { e.stopPropagation(); showEventNameModal(event); }); // 添加删除按钮事件 const deleteBtn eventCard.querySelector(.delete-btn); deleteBtn.addEventListener(click, (e) > { e.stopPropagation(); deleteEvent(event); }); eventsGrid.appendChild(eventCard); }); } // 显示编辑活动名称模态框 function showEventNameModal(event) { document.getElementById(eventNameInput).value event.get(name) || ; document.getElementById(eventNameModal).style.display flex; // 存储当前编辑的活动 currentEditingEvent event; } // 关闭活动名称编辑模态框 function closeEventNameModal() { document.getElementById(eventNameModal).style.display none; currentEditingEvent null; } // 保存活动名称 async function saveEventName() { const newName document.getElementById(eventNameInput).value.trim(); if (!newName) { alert(活动名称不能为空); return; } try { currentEditingEvent.set(name, newName); await currentEditingEvent.save(); // 更新当前页面显示 if (currentEvent && currentEvent.id currentEditingEvent.id) { document.getElementById(eventTitle).textContent newName; } closeEventNameModal(); // 重新加载活动列表 loadEvents(); showStatus(dataStatus, 活动名称已更新, saved); } catch (error) { console.error(更新活动名称失败:, error); showStatus(dataStatus, 更新失败: + error.message, error); } } // 创建新活动 async function addNewEvent() { try { const event new AV.Object(Event); event.set(name, 新活动 + new Date().toLocaleDateString()); event.set(owner, currentUser); event.set(guestCount, 0); await event.save(); loadEvents(); } catch (error) { console.error(创建活动失败:, error); } } // 删除活动 async function deleteEvent(event) { if (!confirm(确定要删除这个活动吗?此操作不可恢复。)) { return; } try { // 同时删除与此活动相关的所有数据 await deleteEventData(event); await event.destroy(); loadEvents(); } catch (error) { console.error(删除活动失败:, error); alert(删除活动失败: + error.message); } } // 删除活动相关数据 async function deleteEventData(event) { // 删除宾客数据 const guestQuery new AV.Query(Guest); guestQuery.equalTo(event, event); guestQuery.equalTo(owner, currentUser); const guests await guestQuery.find(); await AV.Object.destroyAll(guests); // 删除布局数据 const layoutQuery new AV.Query(Layout); layoutQuery.equalTo(event, event); layoutQuery.equalTo(owner, currentUser); const layouts await layoutQuery.find(); await AV.Object.destroyAll(layouts); // 删除宴会信息数据 const banquetQuery new AV.Query(BanquetInfo); banquetQuery.equalTo(event, event); banquetQuery.equalTo(owner, currentUser); const banquetInfos await banquetQuery.find(); await AV.Object.destroyAll(banquetInfos); } // 返回活动列表 function backToEvents() { showEventsPage(); loadEvents(); } // 显示编辑页面 function showEditorPage(event) { document.getElementById(loginPage).style.display none; document.getElementById(eventsPage).style.display none; document.getElementById(editorPage).style.display block; currentEvent event; document.getElementById(eventTitle).textContent event.get(name) || 宴会座位管理系统; // 初始化编辑器 initEditor(event); } // 初始化编辑器 function initEditor(event) { console.log(初始化编辑器:, event.id); // 初始化图表 initChart(); // 生成桌子 generateTables(tableCount); // 填充桌号选择框 populateTableSelect(); // 初始化区域选择下拉框 updateAreaSelectOptions(); // 生成区域配置界面 generateAreaConfigs(); // 加载活动相关数据 loadEventData(event); // 初始化拖拽功能 initDrag(); } // 加载活动数据 async function loadEventData(event) { // 加载布局数据 await loadLayoutData(event); // 加载宴会信息 await loadBanquetInfo(event); // 加载宾客数据 await refreshGuestTable(event); } // 初始化拖拽功能 function initDrag() { // 为舞台、出入口、过道添加拖拽事件 const draggableElements document.getElementById(stage), document.getElementById(entrance1), document.getElementById(entrance2), document.getElementById(aisle) ; draggableElements.forEach(element > { element.addEventListener(mousedown, startDrag); }); // 为桌子添加拖拽事件(使用事件委托) document.getElementById(tableGrid).addEventListener(mousedown, function(e) { if (e.target.classList.contains(table)) { startDrag(e); } }); // 添加全局事件 document.addEventListener(mousemove, doDrag); document.addEventListener(mouseup, endDrag); } // 拖拽相关变量 let isDragging false; let dragElement null; let dragStartX 0; let dragStartY 0; let elementStartX 0; let elementStartY 0; // 开始拖拽 function startDrag(e) { isDragging true; dragElement e.target; // 记录初始位置 dragStartX e.clientX; dragStartY e.clientY; // 获取元素当前位置 const computedStyle window.getComputedStyle(dragElement); elementStartX parseInt(computedStyle.left) || 0; elementStartY parseInt(computedStyle.top) || 0; // 添加拖拽样式 dragElement.classList.add(dragging); // 阻止文本选中 e.preventDefault(); } // 执行拖拽 function doDrag(e) { if (!isDragging || !dragElement) return; // 计算移动距离 const deltaX e.clientX - dragStartX; const deltaY e.clientY - dragStartY; // 更新元素位置 const newLeft elementStartX + deltaX; const newTop elementStartY + deltaY; dragElement.style.left `${newLeft}px`; dragElement.style.top `${newTop}px`; } // 结束拖拽 function endDrag() { if (isDragging && dragElement) { dragElement.classList.remove(dragging); } isDragging false; dragElement null; } // 生成桌子 function generateTables(count) { const tableGrid document.getElementById(tableGrid); tableGrid.innerHTML ; // 更新区域颜色映射 updateAreaColorMap(); // 获取所有区域名称 const areaNames banquetInfo.mainTableText, banquetInfo.groomFamilyText, banquetInfo.groomFriendsText, banquetInfo.brideFamilyText, banquetInfo.brideFriendsText, banquetInfo.area5Text, banquetInfo.area6Text ; for (let i 1; i count; i++) { const table document.createElement(div); table.className table; table.setAttribute(data-number, i); table.id `table-${i}`; table.textContent i; table.style.position absolute; // 检查是否有保存的位置 let savedPosition null; if (layoutData.tables && layoutData.tables.length > 0) { savedPosition layoutData.tables.find(pos > pos.id i); } if (savedPosition) { // 使用保存的位置 table.style.left `${savedPosition.left}px`; table.style.top `${savedPosition.top}px`; } else { // 设置初始位置 const row Math.floor((i - 1) / 10); const col (i - 1) % 10; table.style.top `${5 + row * 8}%`; table.style.left `${5 + col * 9}%`; } // 分配区域和颜色 let areaName banquetInfo.area6Text; // 默认 let colorClass area6; // 默认 // 根据区域配置查找当前桌号所属区域 for (const area of areaNames) { if (areaConfigsarea && i > areaConfigsarea.min && i areaConfigsarea.max) { areaName area; colorClass areaColorMaparea; break; } } table.classList.add(colorClass); tableGrid.appendChild(table); } } // 填充桌号选择框 function populateTableSelect() { const select document.getElementById(guestTableSelect); if(select) { select.innerHTML ; for (let i 1; i tableCount; i++) { const option document.createElement(option); option.value i; option.textContent `${i}号桌`; select.appendChild(option); } } } // 更新区域颜色映射 function updateAreaColorMap() { areaColorMap { banquetInfo.mainTableText: main-table, banquetInfo.groomFamilyText: groom-family, banquetInfo.groomFriendsText: groom-friends, banquetInfo.brideFamilyText: bride-family, banquetInfo.brideFriendsText: bride-friends, banquetInfo.area5Text: area5, banquetInfo.area6Text: area6 }; } // 更新区域选择下拉框 function updateAreaSelectOptions() { const select document.getElementById(guestAreaSelect); if(select) { select.innerHTML ; // 按固定顺序添加区域选项 const areas banquetInfo.mainTableText, banquetInfo.groomFamilyText, banquetInfo.groomFriendsText, banquetInfo.brideFamilyText, banquetInfo.brideFriendsText, banquetInfo.area5Text, banquetInfo.area6Text ; areas.forEach(area > { const option document.createElement(option); option.value area; option.textContent area; select.appendChild(option); }); } } // 生成区域配置界面 function generateAreaConfigs() { const container document.getElementById(areaConfigs); if (!container) return; container.innerHTML ; // 区域顺序 const areas { name: banquetInfo.mainTableText, colorClass: area-main-color }, { name: banquetInfo.groomFamilyText, colorClass: area-groom-family-color }, { name: banquetInfo.groomFriendsText, colorClass: area-groom-friends-color }, { name: banquetInfo.brideFamilyText, colorClass: area-bride-family-color }, { name: banquetInfo.brideFriendsText, colorClass: area-bride-friends-color }, { name: banquetInfo.area5Text, colorClass: area-area5-color }, { name: banquetInfo.area6Text, colorClass: area-area6-color } ; areas.forEach(area > { const configItem document.createElement(div); configItem.className area-config-item; // 主桌有特殊处理 const isMainTable area.name banquetInfo.mainTableText; // 获取当前区域配置,如果没有则使用默认值 const config areaConfigsarea.name || { min: 0, max: 0 }; configItem.innerHTML ` div classarea-label>${area.name}/div> div classarea-range> input typenumber idmin-${area.name} min1 max${tableCount} value${config.min} ${isMainTable ? disabled : }> span>至/span> input typenumber idmax-${area.name} min1 max${tableCount} value${config.max} ${isMainTable ? disabled : }> /div> div classarea-color ${area.colorClass}>/div> ${isMainTable ? div classarea-range-info>主桌固定为1号桌/div> : } `; container.appendChild(configItem); }); } // 保存布局 async function saveLayout() { // 保存区域配置 saveAreaConfigs(); // 收集布局数据 const stage document.getElementById(stage); const entrance1 document.getElementById(entrance1); const entrance2 document.getElementById(entrance2); const aisle document.getElementById(aisle); layoutData { stage: { top: parseInt(stage.style.top), left: parseInt(stage.style.left), visible: layoutData.stage.visible }, entrance1: { top: parseInt(entrance1.style.top), left: parseInt(entrance1.style.left), visible: layoutData.entrance1.visible }, entrance2: { top: parseInt(entrance2.style.top), left: parseInt(entrance2.style.left), visible: layoutData.entrance2.visible }, aisle: { top: parseInt(aisle.style.top), left: parseInt(aisle.style.left), height: parseInt(aisle.style.height), visible: layoutData.aisle.visible }, tables: , areaConfigs: areaConfigs, tableCount: tableCount }; // 收集桌子位置 document.querySelectorAll(#tableGrid .table).forEach(table > { layoutData.tables.push({ id: table.getAttribute(data-number), top: parseInt(table.style.top), left: parseInt(table.style.left) }); }); // 保存布局数据到LeanCloud await saveLayoutDataToCloud(); // 重新生成桌子以应用新的区域配置 generateTables(tableCount); } // 保存布局数据到LeanCloud async function saveLayoutDataToCloud() { try { // 先清除现有布局数据 const Layout AV.Object.extend(Layout); const query new AV.Query(Layout); query.equalTo(event, currentEvent); query.equalTo(owner, currentUser); const existingLayouts await query.find(); if (existingLayouts.length > 0) { await AV.Object.destroyAll(existingLayouts); debugLog(已清除云端现有布局数据); } // 创建新布局 const layoutObj new Layout(); // 设置ACL权限 const acl new AV.ACL(); acl.setPublicReadAccess(true); acl.setPublicWriteAccess(true); layoutObj.setACL(acl); // 设置关联 layoutObj.set(event, currentEvent); layoutObj.set(owner, currentUser); layoutObj.set(data, layoutData); // 保存到云端 await layoutObj.save(); showStatus(layoutSaveStatus, 大厅布局已保存到云端, saved); debugLog(大厅布局已保存); } catch (error) { console.error(保存布局失败:, error); showStatus(layoutSaveStatus, 保存失败: + error.message, error); debugLog(`保存布局失败: ${error.message}`); } } // 保存宴会信息 function saveBanquetInfo() { // 更新宴会信息 banquetInfo.weddingInfo document.getElementById(weddingInfo).value || banquetInfo.weddingInfo; banquetInfo.mainTableText document.getElementById(mainTableText).value || banquetInfo.mainTableText; banquetInfo.groomFamilyText document.getElementById(groomFamilyText).value || banquetInfo.groomFamilyText; banquetInfo.groomFriendsText document.getElementById(groomFriendsText).value || banquetInfo.groomFriendsText; banquetInfo.brideFamilyText document.getElementById(brideFamilyText).value || banquetInfo.brideFamilyText; banquetInfo.brideFriendsText document.getElementById(brideFriendsText).value || banquetInfo.brideFriendsText; banquetInfo.area5Text document.getElementById(area5Text).value || banquetInfo.area5Text; banquetInfo.area6Text document.getElementById(area6Text).value || banquetInfo.area6Text; // 更新显示 updateLegendTexts(); updateBanquetInfo(); // 更新区域颜色映射 updateAreaColorMap(); // 更新区域选择下拉框 updateAreaSelectOptions(); // 重新生成区域配置界面 generateAreaConfigs(); // 重新生成桌子以应用新的区域名称和颜色 generateTables(tableCount); // 保存到LeanCloud saveBanquetInfoToCloud(); } // 更新图例文本显示 function updateLegendTexts() { document.querySelector(.legend-main-text).textContent banquetInfo.mainTableText; document.querySelector(.legend-groom-family-text).textContent banquetInfo.groomFamilyText; document.querySelector(.legend-groom-friends-text).textContent banquetInfo.groomFriendsText; document.querySelector(.legend-bride-family-text).textContent banquetInfo.brideFamilyText; document.querySelector(.legend-bride-friends-text).textContent banquetInfo.brideFriendsText; document.querySelector(.legend-area5-text).textContent banquetInfo.area5Text; document.querySelector(.legend-area6-text).textContent banquetInfo.area6Text; } // 更新宴会信息显示 function updateBanquetInfo() { document.querySelector(.wedding-info).textContent banquetInfo.weddingInfo; } // 保存宴会信息到LeanCloud async function saveBanquetInfoToCloud() { try { // 先清除现有宴会信息 const BanquetInfo AV.Object.extend(BanquetInfo); const query new AV.Query(BanquetInfo); query.equalTo(event, currentEvent); query.equalTo(owner, currentUser); const existingInfo await query.find(); if (existingInfo.length > 0) { await AV.Object.destroyAll(existingInfo); debugLog(已清除云端现有宴会信息); } // 创建新信息 const infoObj new BanquetInfo(); // 设置ACL权限 const acl new AV.ACL(); acl.setPublicReadAccess(true); acl.setPublicWriteAccess(true); infoObj.setACL(acl); // 设置关联 infoObj.set(event, currentEvent); infoObj.set(owner, currentUser); infoObj.set(info, banquetInfo); // 保存到云端 await infoObj.save(); showStatus(dataStatus, 宴会信息已保存到云端, saved); debugLog(宴会信息已保存); } catch (error) { console.error(保存宴会信息失败:, error); showStatus(dataStatus, 保存失败: + error.message, error); debugLog(`保存宴会信息失败: ${error.message}`); } } // 保存区域配置 function saveAreaConfigs() { const areas banquetInfo.mainTableText, banquetInfo.groomFamilyText, banquetInfo.groomFriendsText, banquetInfo.brideFamilyText, banquetInfo.brideFriendsText, banquetInfo.area5Text, banquetInfo.area6Text ; // 更新区域配置 areas.forEach(area > { const minInput document.getElementById(`min-${area}`); const maxInput document.getElementById(`max-${area}`); if (minInput && maxInput) { areaConfigsarea { min: parseInt(minInput.value), max: parseInt(maxInput.value) }; } }); showStatus(dataStatus, 区域配置已保存, saved); debugLog(区域配置已保存); } // 更新桌数 function updateTableCount() { const newCount parseInt(document.getElementById(tableCountInput).value); if (newCount > 1 && newCount 200) { tableCount newCount; // 更新布局数据中的桌数 layoutData.tableCount tableCount; generateTables(tableCount); populateTableSelect(); generateAreaConfigs(); // 保存布局到云端 saveLayoutDataToCloud(); showStatus(dataStatus, `桌数已更新为: ${tableCount}`, saved); } else { showStatus(dataStatus, 桌数必须在1-200之间, error); } } // 切换元素可见性 function toggleVisibility(e) { const elementId e.target.id.replace(Visible, ); layoutDataelementId.visible e.target.checked; // 更新显示 updateLayoutVisibility(); // 保存布局 saveLayoutDataToCloud(); } // 更新布局元素可见性 function updateLayoutVisibility() { document.getElementById(stage).style.display layoutData.stage.visible ? flex : none; document.getElementById(entrance1).style.display layoutData.entrance1.visible ? flex : none; document.getElementById(entrance2).style.display layoutData.entrance2.visible ? flex : none; document.getElementById(aisle).style.display layoutData.aisle.visible ? block : none; } // 显示添加宾客模态框 function showAddGuestModal() { // 重置编辑状态 currentEditingGuest null; document.getElementById(guestModalNameInput).value ; document.getElementById(guestTableSelect).value 1; document.getElementById(guestAreaSelect).value banquetInfo.mainTableText; document.getElementById(guestStatus).value false; document.getElementById(modalHeader).textContent 添加新宾客; document.getElementById(guestModal).style.display flex; } // 关闭宾客模态框 function closeGuestModal() { document.getElementById(guestModal).style.display none; currentEditingGuest null; // 取消时重置编辑状态 } // 保存宾客 async function saveGuest() { const name document.getElementById(guestModalNameInput).value.trim(); if (!name) { alert(请输入宾客姓名); return; } const table document.getElementById(guestTableSelect).value; const area document.getElementById(guestAreaSelect).value; const status document.getElementById(guestStatus).value true; if (currentEditingGuest) { // 更新现有宾客 try { currentEditingGuest.set(name, name); currentEditingGuest.set(table, table); currentEditingGuest.set(area, area); currentEditingGuest.set(confirmed, status); await currentEditingGuest.save(); document.getElementById(guestModal).style.display none; showStatus(dataStatus, 宾客信息已更新, saved); refreshGuestTable(); debugLog(`宾客信息已更新: ${name}`); // 重置编辑状态 currentEditingGuest null; } catch (error) { console.error(更新失败:, error); showStatus(dataStatus, 更新失败, error); } } else { // 添加新宾客 saveGuestToCloud(name, table, area, status); document.getElementById(guestModal).style.display none; } } // 保存宾客到LeanCloud async function saveGuestToCloud(name, table, area, status) { try { // 创建Guest对象 const Guest AV.Object.extend(Guest); const guest new Guest(); // 设置ACL权限 const acl new AV.ACL(); acl.setPublicReadAccess(true); acl.setPublicWriteAccess(true); guest.setACL(acl); // 设置关联 guest.set(event, currentEvent); guest.set(owner, currentUser); // 设置数据 guest.set(name, name); guest.set(table, table); guest.set(area, area); guest.set(confirmed, status); // 保存到云端 await guest.save(); // 更新活动的宾客数量 await updateEventGuestCount(); showStatus(dataStatus, 宾客信息已保存到云端, saved); debugLog(`宾客数据已保存: ${name} - 桌号${table}`); // 刷新表格 refreshGuestTable(); } catch (error) { console.error(保存宾客失败:, error); showStatus(dataStatus, 保存失败: + error.message, error); debugLog(`保存宾客失败: ${error.message}`); } } // 更新活动的宾客数量 async function updateEventGuestCount() { try { // 查询当前活动的宾客数量 const query new AV.Query(Guest); query.equalTo(event, currentEvent); query.equalTo(owner, currentUser); const count await query.count(); // 更新当前活动的guestCount字段 currentEvent.set(guestCount, count); await currentEvent.save(); debugLog(`更新活动宾客数量: ${count}`); } catch (error) { console.error(更新活动宾客数量失败:, error); debugLog(`更新活动宾客数量失败: ${error.message}`); } } // 搜索宾客 async function searchGuest() { const name document.getElementById(adminSearch).value.trim(); if (!name) { alert(请输入宾客姓名); return; } // 从LeanCloud查询数据 try { const query new AV.Query(Guest); query.equalTo(event, currentEvent); query.equalTo(owner, currentUser); query.equalTo(name, name); const guests await query.find(); if (guests.length 0) { alert(未找到匹配的宾客信息); return; } const guest guests0; // 高亮显示搜索结果 const rows document.querySelectorAll(#guestTableBody tr); rows.forEach(row > { row.style.backgroundColor ; }); // 找到匹配的行并高亮 let found false; rows.forEach(row > { const nameCell row.cells0; if (nameCell.textContent name) { row.style.backgroundColor #e3f2fd; row.scrollIntoView({behavior: smooth, block: center}); found true; } }); if (!found) { alert(宾客在列表中未找到,请刷新数据); } debugLog(`宾客查询成功: ${name}`); } catch (error) { console.error(查询失败:, error); alert(查询失败,请稍后再试); debugLog(`宾客查询失败: ${error.message}`); } } // 刷新宾客表格 async function refreshGuestTable() { try { showSyncNotification(true); debugLog(正在刷新宾客数据...); const query new AV.Query(Guest); query.equalTo(event, currentEvent); query.equalTo(owner, currentUser); query.limit(1000); // 设置limit以获取超过100条记录 const guests await query.find(); const guestTableBody document.getElementById(guestTableBody); if(guestTableBody) { guestTableBody.innerHTML ; debugLog(`从LeanCloud获取到 ${guests.length} 条宾客记录`); let totalConfirmed 0; let totalPending 0; guests.forEach(guest > { const confirmed guest.get(confirmed); const tr document.createElement(tr); tr.innerHTML ` td>${guest.get(name)}/td> td>${guest.get(table)}/td> td>${guest.get(area)}/td> td> span classattendance-status ${confirmed ? attendance-yes : attendance-no}> span classstatus-indicator ${confirmed ? status-confirmed : status-pending}>/span> ${confirmed ? 已确认 : 未确认} /span> /td> td classadmin-actions> button classedit-btn data-id${guest.id}>i classfas fa-edit>/i> 编辑/button> button classdelete-btn data-id${guest.id}>i classfas fa-trash>/i> 删除/button> /td> `; guestTableBody.appendChild(tr); // 统计出席状态 if (confirmed) { totalConfirmed++; } else { totalPending++; } }); // 更新统计信息 document.getElementById(statTotal).textContent guests.length; document.getElementById(statConfirmed).textContent totalConfirmed; document.getElementById(statPending).textContent totalPending; debugLog(`数据统计更新:总宾客数${guests.length}, 已确认${totalConfirmed}, 待确认${totalPending}`); // 更新图表 if (window.attendanceChart) { window.attendanceChart.data.datasets0.data totalConfirmed, totalPending; window.attendanceChart.update(); debugLog(图表已更新); } showStatus(dataStatus, `成功加载 ${guests.length} 条数据`, saved); // 更新活动的宾客数量 await updateEventGuestCount(); // 添加编辑和删除事件监听 addEditDeleteListeners(); } } catch (error) { console.error(刷新数据失败:, error); showStatus(dataStatus, 刷新失败: + error.message, error); debugLog(`刷新数据失败: ${error.message}`); } finally { showSyncNotification(false); } } // 添加编辑和删除事件监听 function addEditDeleteListeners() { // 编辑按钮 document.querySelectorAll(.edit-btn).forEach(btn > { btn.addEventListener(click, async function() { const guestId this.getAttribute(data-id); try { const query new AV.Query(Guest); currentEditingGuest await query.get(guestId); document.getElementById(guestModalNameInput).value currentEditingGuest.get(name); document.getElementById(guestTableSelect).value currentEditingGuest.get(table); document.getElementById(guestAreaSelect).value currentEditingGuest.get(area); document.getElementById(guestStatus).value currentEditingGuest.get(confirmed) ? true : false; document.getElementById(modalHeader).textContent 编辑宾客信息; document.getElementById(guestModal).style.display flex; } catch (error) { console.error(获取宾客信息失败:, error); showStatus(dataStatus, 获取信息失败, error); } }); }); // 删除按钮 document.querySelectorAll(.delete-btn).forEach(btn > { btn.addEventListener(click, async function() { const guestId this.getAttribute(data-id); if (confirm(确定要删除此宾客信息吗?此操作不可恢复。)) { try { const query new AV.Query(Guest); const guest await query.get(guestId); await guest.destroy(); // 更新活动的宾客数量 await updateEventGuestCount(); showStatus(dataStatus, 宾客信息已删除, saved); refreshGuestTable(); debugLog(`宾客信息已删除: ID${guestId}`); } catch (error) { console.error(删除失败:, error); showStatus(dataStatus, 删除失败: + error.message, error); } } }); }); } // 触发Excel导入 function triggerExcelImport() { document.getElementById(excelFileInput).click(); } // 处理Excel导入 function handleExcelImport(e) { const file e.target.files0; if (!file) return; document.getElementById(importInfo).textContent `已选择: ${file.name} (准备导入)`; document.getElementById(importInfo).style.background #fff8e1; document.getElementById(importInfo).style.borderLeftColor #ffc107; debugLog(`已选择Excel文件: ${file.name}`); const reader new FileReader(); reader.onload function(event) { const data new Uint8Array(event.target.result); const workbook XLSX.read(data, {type: array}); // 获取第一个工作表 const worksheet workbook.Sheetsworkbook.SheetNames0; // 将工作表转换为JSON const jsonData XLSX.utils.sheet_to_json(worksheet); if (jsonData.length 0) { showStatus(dataStatus, Excel文件中未找到有效数据, error); document.getElementById(importInfo).textContent 未找到有效数据,导入失败; document.getElementById(importInfo).style.background #ffebee; document.getElementById(importInfo).style.borderLeftColor #f44336; debugLog(Excel文件中未找到有效数据); return; } debugLog(`找到 ${jsonData.length} 条有效宾客记录`); document.getElementById(importInfo).textContent `找到 ${jsonData.length} 条记录,正在导入...`; document.getElementById(importInfo).style.background #e8f5e9; document.getElementById(importInfo).style.borderLeftColor #4CAF50; // 转换数据结构 const guests jsonData.map(item > { // 兼容多种列名格式 const name item姓名 || itemname || item宾客姓名 || ; const table item桌号 || itemtable || item桌号 || ; const area item区域 || itemarea || item区域名称 || ; // 处理出席状态 let confirmed false; const status item出席状态 || itemconfirmed || itemstatus || ; if (typeof status boolean) { confirmed status; } else if (typeof status string) { confirmed status.toLowerCase() 是 || status.toLowerCase() true || status.toLowerCase() 出席 || status.toLowerCase() yes; } else if (typeof status number) { confirmed status 1; } return { name: name, table: table.toString(), // 确保桌号为字符串 area: area, confirmed: confirmed }; }); importToLeanCloud(guests); }; reader.readAsArrayBuffer(file); } // 导入数据到LeanCloud async function importToLeanCloud(guests) { const Guest AV.Object.extend(Guest); const batchSize 20; // 分批处理防止超限 let importedCount 0; let errorCount 0; let errors ; const progressBar document.getElementById(importProgress); // 显示进度条 progressBar.style.width 0%; // 先清除现有数据(可选) try { debugLog(正在清除现有数据...); const query new AV.Query(Guest); query.equalTo(event, currentEvent); query.equalTo(owner, currentUser); const allGuests await query.find(); await AV.Object.destroyAll(allGuests); debugLog(`成功清除 ${allGuests.length} 条现有数据`); } catch (error) { debugLog(`清除数据时出错: ${error.message}`); } for (let i 0; i guests.length; i + batchSize) { const batch guests.slice(i, i + batchSize); const objects batch.map(guest > { const obj new Guest(); // 设置ACL权限 const acl new AV.ACL(); acl.setPublicReadAccess(true); acl.setPublicWriteAccess(true); obj.setACL(acl); // 设置关联 obj.set(event, currentEvent); obj.set(owner, currentUser); // 设置数据 obj.set(name, guest.name); obj.set(table, guest.table); obj.set(area, guest.area); obj.set(confirmed, guest.confirmed); return obj; }); try { await AV.Object.saveAll(objects); importedCount + batch.length; debugLog(`成功导入第 ${i+1} 到 ${i+batch.length} 条数据`); // 更新进度 const progress Math.round((importedCount / guests.length) * 100); progressBar.style.width `${progress}%`; document.getElementById(importInfo).textContent `已导入 ${importedCount}/${guests.length} 条数据`; showStatus(dataStatus, `已导入 ${importedCount}/${guests.length} 条数据`, saved); } catch (error) { console.error(批量导入失败:, error); errorCount + batch.length; // 检查是否为CORS错误 let errorMsg error.message; if (error.message.includes(Access-Control-Allow-Origin)) { errorMsg 域请求被阻止:请检查LeanCloud安全中心Web域名设置; } else if (error.message.includes(404)) { errorMsg ServerURL配置错误:请确保使用正确的格式 https://AppID前8位.api.lncldapi.com; } errors.push(`记录 ${i+1}-${i+batch.length}: ${errorMsg}`); showStatus(dataStatus, `导入失败: ${errorMsg}`, error); } } if (importedCount guests.length) { // 更新活动的宾客数量 await updateEventGuestCount(); showStatus(dataStatus, `成功导入 ${importedCount} 条数据`, saved); document.getElementById(importInfo).textContent `成功导入 ${importedCount} 条数据`; document.getElementById(importInfo).style.background #e8f5e9; document.getElementById(importInfo).style.borderLeftColor #4CAF50; debugLog(`成功导入 ${importedCount} 条数据`); // 刷新表格 refreshGuestTable(); } else { const msg `导入完成,成功: ${importedCount}, 失败: ${errorCount}`; showStatus(dataStatus, msg, error); document.getElementById(importInfo).textContent msg; document.getElementById(importInfo).style.background #ffebee; document.getElementById(importInfo).style.borderLeftColor #f44336; debugLog(msg); // 显示前5个错误 if (errors.length > 0) { const errorDetails errors.slice(0, 5).join(br>); document.getElementById(importInfo).innerHTML + `div stylemargin-top:10px;color:#d32f2f;font-size:0.9em;>错误示例:br>${errorDetails}/div>`; } } } // 下载Excel模板 function downloadExcelTemplate(e) { e.preventDefault(); // 创建工作簿 const wb XLSX.utils.book_new(); // 创建工作表数据 - 使用正确的列名 const data 姓名, 桌号, 区域, 出席状态, 张三, 1, 主桌, 是, 李四, 5, 男方亲戚, 否, 王五, 12, 男方朋友, 是, 赵六, 25, 女方亲戚, 是, 钱七, 35, 女方朋友, 否, 孙八, 45, 公司同事, 是, 周九, 55, 备桌, 否 ; // 创建工作表 const ws XLSX.utils.aoa_to_sheet(data); // 将工作表添加到工作簿 XLSX.utils.book_append_sheet(wb, ws, 宾客数据); // 生成Excel文件并下载 XLSX.writeFile(wb, 宾客导入模板.xlsx); debugLog(Excel模板下载成功); } // 初始化图表 function initChart() { const ctx document.getElementById(attendanceChart); if(ctx) { window.attendanceChart new Chart(ctx.getContext(2d), { type: doughnut, data: { labels: 已确认出席, 待确认, datasets: { data: 0, 0, backgroundColor: #4CAF50, #FF9800, borderWidth: 1 } }, options: { responsive: true, plugins: { legend: { position: bottom, }, title: { display: true, text: 宾客出席情况统计, font: { size: 16 } } } } }); } } // 加载布局数据 async function loadLayoutData(event) { try { const query new AV.Query(Layout); query.equalTo(event, event); query.equalTo(owner, currentUser); const layoutObj await query.first(); if (!layoutObj) { debugLog(云端没有保存的布局数据); return; } layoutData layoutObj.get(data); const stage document.getElementById(stage); const entrance1 document.getElementById(entrance1); const entrance2 document.getElementById(entrance2); const aisle document.getElementById(aisle); if (layoutData.stage) { stage.style.top `${layoutData.stage.top}px`; stage.style.left `${layoutData.stage.left}px`; document.getElementById(stageVisible).checked layoutData.stage.visible; } if (layoutData.entrance1) { entrance1.style.top `${layoutData.entrance1.top}px`; entrance1.style.left `${layoutData.entrance1.left}px`; document.getElementById(entrance1Visible).checked layoutData.entrance1.visible; } if (layoutData.entrance2) { entrance2.style.top `${layoutData.entrance2.top}px`; entrance2.style.left `${layoutData.entrance2.left}px`; document.getElementById(entrance2Visible).checked layoutData.entrance2.visible; } if (layoutData.aisle) { aisle.style.top `${layoutData.aisle.top}px`; aisle.style.left `${layoutData.aisle.left}px`; aisle.style.height `${layoutData.aisle.height}px`; document.getElementById(aisleVisible).checked layoutData.aisle.visible; } // 应用区域配置 if (layoutData.areaConfigs) { areaConfigs layoutData.areaConfigs; } // 更新桌数 if (layoutData.tableCount) { tableCount layoutData.tableCount; document.getElementById(tableCountInput).value tableCount; } // 重新生成区域配置界面 generateAreaConfigs(); // 重新生成桌子以应用区域配置和桌数 generateTables(tableCount); // 应用桌子位置 if (layoutData.tables && layoutData.tables.length > 0) { layoutData.tables.forEach(tablePos > { const table document.querySelector(`.tabledata-number${tablePos.id}`); if (table) { table.style.top `${tablePos.top}px`; table.style.left `${tablePos.left}px`; } }); } // 更新可见性 updateLayoutVisibility(); debugLog(大厅布局数据已加载); } catch (error) { console.error(加载布局失败:, error); debugLog(`加载布局失败: ${error.message}`); } } // 加载宴会信息 async function loadBanquetInfo(event) { try { const query new AV.Query(BanquetInfo); query.equalTo(event, event); query.equalTo(owner, currentUser); const infoObj await query.first(); if (!infoObj) { debugLog(云端没有保存的宴会信息); return; } banquetInfo infoObj.get(info); // 更新表单字段 document.getElementById(weddingInfo).value banquetInfo.weddingInfo; document.getElementById(mainTableText).value banquetInfo.mainTableText; document.getElementById(groomFamilyText).value banquetInfo.groomFamilyText; document.getElementById(groomFriendsText).value banquetInfo.groomFriendsText; document.getElementById(brideFamilyText).value banquetInfo.brideFamilyText; document.getElementById(brideFriendsText).value banquetInfo.brideFriendsText; document.getElementById(area5Text).value banquetInfo.area5Text; document.getElementById(area6Text).value banquetInfo.area6Text; // 更新显示 updateLegendTexts(); updateBanquetInfo(); // 更新区域颜色映射 updateAreaColorMap(); // 更新区域选择下拉框 updateAreaSelectOptions(); // 重新生成区域配置界面 generateAreaConfigs(); // 重新生成桌子以应用新的区域名称和颜色 generateTables(tableCount); debugLog(宴会信息已加载); } catch (error) { console.error(加载宴会信息失败:, error); debugLog(`加载宴会信息失败: ${error.message}`); } } // 显示状态消息 function showStatus(elementId, message, type) { const element document.getElementById(elementId); if(element) { element.textContent message; element.className data-status + type; element.style.display block; setTimeout(() > { element.style.display none; }, 3000); } } // 显示同步通知 function showSyncNotification(show) { const notification document.getElementById(syncNotification); if(notification) { notification.style.display show ? flex : none; } } // 生成小程序码 async function generateMiniProgramQrcode(retryCount 0) { if (!currentEvent) { showQrcodeStatus(请先选择或创建活动, error); return; } try { showSyncNotification(true); debugLog(正在生成小程序码...); // 显示加载状态 showQrcodeStatus(正在生成小程序码,请稍候..., success); // 调用LeanCloud云函数 const result await AV.Cloud.run(generateMiniProgramQrcode, { eventId: currentEvent.id, eventName: currentEvent.get(name) || 宴会活动, pagePath: `pages/index/index?eventId${currentEvent.id}`, // 这里根据实际情况修改,应该是小程序中查看席位的页面路径 width: 430 }); if (!result || !result.qrcodeUrl) { throw new Error(未收到有效的二维码URL); } debugLog(收到二维码URL:, result.qrcodeUrl); // 显示小程序码 const qrcodeContainer document.getElementById(qrcodeContainer); const qrcodeImage document.getElementById(qrcodeImage); // 先清空容器 qrcodeImage.innerHTML ; qrcodeContainer.style.display block; // 使用新的Image对象确保图片加载完成 const img new Image(); // 设置超时处理 const loadTimeout setTimeout(() > { if (!img.complete) { console.warn(图片加载超时); showQrcodeStatus(图片加载超时,请重试, error); showSyncNotification(false); } }, 10000); // 10秒超时 img.onload function() { clearTimeout(loadTimeout); qrcodeImage.appendChild(img); // 显示活动信息 document.getElementById(qrcodeEventId).textContent currentEvent.id; document.getElementById(qrcodeEventName).textContent currentEvent.get(name) || 未命名活动; document.getElementById(qrcodeTime).textContent new Date().toLocaleString(); debugLog(小程序码生成成功); showQrcodeStatus(小程序码生成成功, success); showSyncNotification(false); }; img.onerror function() { clearTimeout(loadTimeout); console.error(图片加载失败); showQrcodeStatus(小程序码加载失败,请重试, error); showSyncNotification(false); // 尝试直接在新窗口打开URL以验证是否有效 window.open(result.qrcodeUrl, _blank); }; img.src result.qrcodeUrl; img.alt 小程序二维码; img.style.maxWidth 250px; img.style.border 1px solid #ddd; img.style.borderRadius 8px; img.style.boxShadow 0 4px 8px rgba(0, 0, 0, 0.1); } catch (error) { console.error(生成小程序码失败:, error); showSyncNotification(false); // 检查是否为token过期错误,并且重试次数小于3次 if (error.code 401 && error.retry && retryCount 3) { debugLog(`token过期,第${retryCount + 1}次重试...`); showQrcodeStatus(`token过期,正在重试 (${retryCount + 1}/3)...`, success); // 等待1秒后重试 await new Promise(resolve > setTimeout(resolve, 1000)); return generateMiniProgramQrcode(retryCount + 1); } let errorMsg error.message; // 提供更详细的错误信息 if (error.code) { errorMsg + ` (错误代码: ${error.code})`; } if (error.message) { errorMsg + `: ${error.message}`; } showQrcodeStatus(生成失败: + errorMsg, error); debugLog(`生成小程序码失败: ${error.message}`); } finally { showSyncNotification(false); } } // 下载二维码 function downloadQrcode() { const qrcodeImage document.querySelector(#qrcodeImage img); if (!qrcodeImage) { alert(请先生成小程序码); return; } // 创建一个临时链接进行下载 const link document.createElement(a); link.href qrcodeImage.src; link.download `小程序码_${currentEvent.id}_${new Date().getTime()}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); debugLog(小程序码下载成功); } // 显示小程序码状态消息 function showQrcodeStatus(message, type) { const statusElement document.getElementById(qrcodeStatus); if (statusElement) { statusElement.textContent message; statusElement.className `qrcode-status ${type}`; statusElement.style.display block; // 5秒后自动隐藏 setTimeout(() > { statusElement.style.display none; }, 5000); } } // 调试日志 function debugLog(message) { const debugConsole document.getElementById(debugConsole); if(debugConsole) { const logEntry document.createElement(div); logEntry.textContent `${new Date().toLocaleTimeString()} ${message}`; debugConsole.appendChild(logEntry); debugConsole.scrollTop debugConsole.scrollHeight; } } // 清除调试控制台 function clearDebugConsole() { document.getElementById(debugConsole).innerHTML ; debugLog(调试日志已清除); } // 页面加载时初始化 window.addEventListener(DOMContentLoaded, init); /script>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]