Files
xsynergy-android/ui.html

2561 lines
88 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XSynergy - AR远程协作APP设计原型</title>
<style>
/* CSS Reset and Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* CSS Variables - Global Design System */
:root {
/* Color System */
--primary: #0A7CFF;
--primary-dark: #0066cc;
--primary-light: #4da6ff;
--secondary: #F0F8FF;
--accent: #FFD700;
--danger: #FF4D4F;
--danger-dark: #cc0000;
--success: #52C41A;
--warning: #faad14;
--text-primary: #333333;
--text-secondary: #666666;
--text-tertiary: #888888;
--text-white: #FFFFFF;
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--bg-tertiary: #f8f9fa;
--border-primary: #e0e0e0;
--border-secondary: #d0d0d0;
/* Spacing System */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 12px;
--spacing-lg: 16px;
--spacing-xl: 20px;
--spacing-xxl: 24px;
--spacing-xxxl: 32px;
--spacing-xxxxl: 40px;
--spacing-xxxxxl: 60px;
/* Typography */
--font-size-xs: 12px;
--font-size-sm: 14px;
--font-size-base: 16px;
--font-size-lg: 18px;
--font-size-xl: 20px;
--font-size-xxl: 24px;
--font-size-xxxl: 32px;
/* Border Radius */
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 20px;
--radius-full: 50%;
/* Shadows */
--shadow-sm: 0 1px 3px rgba(0,0,0,0.1);
--shadow-md: 0 2px 8px rgba(0,0,0,0.1);
--shadow-lg: 0 4px 12px rgba(0,0,0,0.15);
--shadow-xl: 0 10px 40px rgba(0,0,0,0.15);
/* Transitions */
--transition-fast: 0.15s ease;
--transition-normal: 0.3s ease;
--transition-slow: 0.5s ease;
/* Z-index Scale */
--z-dropdown: 1000;
--z-sticky: 1020;
--z-fixed: 1030;
--z-modal-backdrop: 1040;
--z-modal: 1050;
--z-popover: 1060;
--z-tooltip: 1070;
}
/* Base Typography */
html {
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font-family: 'Source Han Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: var(--bg-secondary);
color: var(--text-primary);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: var(--spacing-xl);
line-height: 1.5;
}
/* Focus styles for accessibility */
*:focus {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
/* Button reset */
button {
font-family: inherit;
cursor: pointer;
border: none;
background: none;
padding: 0;
}
/* Input reset */
input {
font-family: inherit;
font-size: inherit;
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideInUp {
from { transform: translateY(100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-2px); }
75% { transform: translateX(2px); }
}
/* Utility Classes */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.loading {
position: relative;
pointer-events: none;
}
.loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid var(--border-primary);
border-top-color: var(--primary);
border-radius: var(--radius-full);
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Phone Container */
.phone-container {
width: 375px;
height: 812px;
background: var(--bg-primary);
border-radius: 30px;
box-shadow: var(--shadow-xl);
overflow: hidden;
position: relative;
transition: transform var(--transition-normal);
}
.phone-container:hover {
transform: translateY(-2px);
}
/* Screen Management */
.screen {
width: 100%;
height: 100%;
display: none;
flex-direction: column;
animation: fadeIn var(--transition-normal);
}
.screen.active {
display: flex;
}
/* Prevent scroll during transitions */
.screen-transitioning {
overflow: hidden;
}
/* Status Bar */
.status-bar {
height: 44px;
background: var(--text-primary);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 var(--spacing-lg);
color: var(--text-white);
font-size: var(--font-size-sm);
font-weight: 500;
}
/* Navigation Controls */
.nav-controls {
position: fixed;
bottom: var(--spacing-xl);
left: 50%;
transform: translateX(-50%);
display: flex;
gap: var(--spacing-sm);
z-index: var(--z-fixed);
background: rgba(255,255,255,0.95);
padding: var(--spacing-sm);
border-radius: var(--radius-xl);
backdrop-filter: blur(10px);
box-shadow: var(--shadow-lg);
}
.nav-btn {
padding: var(--spacing-sm) var(--spacing-lg);
border: 1px solid var(--primary);
background: var(--bg-primary);
color: var(--primary);
border-radius: var(--radius-xl);
cursor: pointer;
transition: all var(--transition-fast);
font-size: var(--font-size-xs);
font-weight: 500;
position: relative;
overflow: hidden;
}
.nav-btn:hover {
background: var(--secondary);
transform: translateY(-1px);
}
.nav-btn:active {
transform: translateY(0);
}
.nav-btn.active {
background: var(--primary);
color: var(--text-white);
box-shadow: var(--shadow-sm);
}
.nav-btn:focus {
outline: 2px solid var(--primary-light);
outline-offset: 2px;
}
/* Login Screen */
.login-screen {
padding: var(--spacing-xxxxxl) var(--spacing-xxxl);
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
min-height: calc(100% - 44px);
}
.logo {
font-size: var(--font-size-xxxl);
font-weight: 700;
color: var(--primary);
margin-bottom: var(--spacing-xxxxxl);
letter-spacing: -0.5px;
}
.input-group {
margin-bottom: var(--spacing-xxl);
text-align: left;
position: relative;
}
.input-field {
width: 100%;
height: 48px;
padding: 0 var(--spacing-lg);
border: 1px solid var(--border-primary);
border-radius: var(--radius-md);
background: var(--bg-tertiary);
font-size: var(--font-size-base);
transition: all var(--transition-fast);
color: var(--text-primary);
}
.input-field:focus {
outline: none;
border-color: var(--primary);
background: var(--bg-primary);
box-shadow: 0 0 0 3px rgba(10, 124, 255, 0.1);
}
.input-field::placeholder {
color: var(--text-tertiary);
}
.input-field:disabled {
background: var(--bg-secondary);
color: var(--text-secondary);
cursor: not-allowed;
}
/* Input validation states */
.input-field.error {
border-color: var(--danger);
background: rgba(255, 77, 79, 0.05);
}
.input-field.success {
border-color: var(--success);
background: rgba(82, 196, 26, 0.05);
}
.code-input-group {
display: flex;
gap: var(--spacing-md);
align-items: center;
}
.code-input {
flex: 1;
}
.code-btn {
height: 48px;
padding: 0 var(--spacing-lg);
background: var(--primary);
color: var(--text-white);
border: none;
border-radius: var(--radius-md);
cursor: pointer;
font-size: var(--font-size-sm);
font-weight: 500;
transition: all var(--transition-fast);
position: relative;
overflow: hidden;
}
.code-btn:hover {
background: var(--primary-dark);
}
.code-btn:active {
transform: scale(0.98);
}
.code-btn:disabled {
background: var(--text-tertiary);
cursor: not-allowed;
transform: none;
}
.code-btn.loading {
color: transparent;
}
.primary-btn {
width: 100%;
height: 48px;
background: var(--primary);
color: var(--text-white);
border: none;
border-radius: var(--radius-md);
font-size: var(--font-size-base);
font-weight: 600;
cursor: pointer;
margin: var(--spacing-xxxl) 0;
transition: all var(--transition-fast);
position: relative;
overflow: hidden;
}
.primary-btn:hover {
background: var(--primary-dark);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.primary-btn:active {
transform: translateY(0);
}
.primary-btn:focus {
outline: 2px solid var(--primary-light);
outline-offset: 2px;
}
.primary-btn:disabled {
background: var(--text-tertiary);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 8px;
margin: 16px 0;
justify-content: flex-start;
}
.links {
display: flex;
justify-content: space-between;
margin-top: 16px;
}
.link {
color: var(--primary);
text-decoration: none;
font-size: 14px;
}
/* Dashboard */
.dashboard {
flex: 1;
padding: var(--spacing-xl);
padding-bottom: 80px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.greeting {
font-size: var(--font-size-xxl);
font-weight: 700;
color: var(--text-primary);
margin-bottom: var(--spacing-xxxl);
line-height: 1.2;
}
.action-section {
margin-bottom: var(--spacing-xxxxl);
}
.large-btn {
width: 100%;
height: 60px;
background: var(--primary);
color: var(--text-white);
border: none;
border-radius: var(--radius-lg);
font-size: var(--font-size-lg);
font-weight: 600;
margin-bottom: var(--spacing-lg);
cursor: pointer;
transition: all var(--transition-fast);
position: relative;
overflow: hidden;
}
.large-btn:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.large-btn:active {
transform: translateY(0);
}
.large-btn:focus {
outline: 2px solid var(--primary-light);
outline-offset: 2px;
}
.large-btn:disabled {
background: var(--text-tertiary);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.secondary-btn {
width: 100%;
height: 48px;
background: var(--bg-primary);
color: var(--primary);
border: 2px solid var(--primary);
border-radius: var(--radius-md);
font-size: var(--font-size-base);
margin-bottom: var(--spacing-lg);
cursor: pointer;
transition: all var(--transition-fast);
font-weight: 500;
}
.secondary-btn:hover {
background: var(--secondary);
border-color: var(--primary-dark);
color: var(--primary-dark);
}
.secondary-btn:active {
transform: scale(0.98);
}
.text-btn {
background: none;
border: none;
color: var(--primary);
font-size: 16px;
cursor: pointer;
text-decoration: underline;
}
.section-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.section-title::before {
content: '';
width: 4px;
height: 20px;
background: var(--primary);
border-radius: 2px;
}
.meeting-card {
background: var(--bg-primary);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
box-shadow: var(--shadow-md);
margin-bottom: var(--spacing-md);
position: relative;
transition: all var(--transition-fast);
border: 1px solid transparent;
}
.meeting-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
border-color: var(--primary-light);
}
.meeting-card:focus-within {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
.meeting-title {
font-size: var(--font-size-base);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-sm);
line-height: 1.4;
}
.meeting-meta {
color: var(--text-secondary);
font-size: var(--font-size-sm);
line-height: 1.4;
}
.status-badge {
position: absolute;
top: var(--spacing-lg);
right: var(--spacing-lg);
background: var(--accent);
color: var(--text-primary);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-weight: 500;
animation: pulse 2s infinite;
}
.join-btn {
width: 100%;
height: 36px;
background: var(--success);
color: white;
border: none;
border-radius: 6px;
font-size: 14px;
margin-top: 12px;
cursor: pointer;
}
/* Bottom Tab Bar */
.bottom-tabs {
position: absolute;
bottom: 0;
width: 100%;
height: 80px;
background: var(--bg-primary);
border-top: 1px solid var(--border-primary);
display: flex;
align-items: center;
justify-content: space-around;
padding-bottom: var(--spacing-lg);
z-index: var(--z-fixed);
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-xs);
cursor: pointer;
color: var(--text-secondary);
transition: all var(--transition-fast);
padding: var(--spacing-sm);
border-radius: var(--radius-md);
position: relative;
}
.tab-item:hover {
color: var(--primary);
background: var(--secondary);
}
.tab-item:focus {
outline: 2px solid var(--primary-light);
outline-offset: 2px;
}
.tab-item.active {
color: var(--primary);
background: rgba(10, 124, 255, 0.1);
}
.tab-item.active .tab-icon {
transform: scale(1.1);
}
.tab-icon {
width: 24px;
height: 24px;
background: currentColor;
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
}
.tab-icon.home { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m3 12 2-2m0 0 7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6'/%3E%3C/svg%3E"); }
.tab-icon.contacts { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z'/%3E%3C/svg%3E"); }
.tab-icon.history { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z'/%3E%3C/svg%3E"); }
.tab-icon.profile { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z'/%3E%3C/svg%3E"); }
.tab-label {
font-size: var(--font-size-xs);
font-weight: 500;
transition: all var(--transition-fast);
}
/* AR Session Screen */
.ar-session {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* AR Camera View - Optimized for ARCore */
.camera-view {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, #2196f3, #21cbf3);
opacity: 0.8;
z-index: 1;
}
/* AR Annotation Layer - 3D Space */
.ar-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 2;
}
/* AR Top Bar - Status and Controls */
.top-bar {
position: absolute;
top: 44px;
left: 0;
right: 0;
height: 60px;
background: rgba(0,0,0,0.7);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 var(--spacing-lg);
pointer-events: all;
z-index: 10;
backdrop-filter: blur(10px);
}
.session-info {
display: flex;
align-items: center;
gap: var(--spacing-md);
color: var(--text-white);
}
.timer {
font-size: var(--font-size-base);
font-weight: 600;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
}
.network-status {
width: 20px;
height: 20px;
background: var(--success);
border-radius: var(--radius-full);
position: relative;
animation: pulse 2s infinite;
}
.network-status.weak {
background: var(--warning);
}
.network-status.offline {
background: var(--danger);
}
/* Hangup Button - Material Design */
.hangup-btn {
width: 48px;
height: 48px;
background: var(--danger);
border: none;
border-radius: var(--radius-full);
color: var(--text-white);
font-size: var(--font-size-lg);
cursor: pointer;
transition: all var(--transition-fast);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.hangup-btn:hover {
background: var(--danger-dark);
transform: scale(1.05);
}
.hangup-btn:active {
transform: scale(0.95);
}
.hangup-btn:focus {
outline: 2px solid var(--danger);
outline-offset: 2px;
}
/* Video Picture-in-Picture */
.video-pip {
position: absolute;
top: 120px;
right: var(--spacing-lg);
width: 120px;
height: 160px;
background: rgba(0,0,0,0.8);
border-radius: var(--radius-lg);
border: 2px solid var(--text-white);
pointer-events: all;
cursor: pointer;
z-index: 5;
transition: all var(--transition-fast);
overflow: hidden;
}
.video-pip:hover {
transform: scale(1.05);
border-color: var(--primary-light);
}
.video-pip.dragging {
opacity: 0.8;
transform: scale(1.1);
}
/* AR Bottom Controls - Material Design */
.bottom-controls {
position: absolute;
bottom: var(--spacing-xl);
left: var(--spacing-lg);
right: var(--spacing-lg);
height: 80px;
background: rgba(0,0,0,0.8);
border-radius: 40px;
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing-lg);
pointer-events: all;
z-index: 10;
backdrop-filter: blur(10px);
transition: all var(--transition-fast);
}
.bottom-controls.collapsed {
transform: translateY(100px);
opacity: 0.7;
}
/* Control Buttons - Material Design */
.control-btn {
width: 48px;
height: 48px;
border-radius: var(--radius-full);
border: none;
background: rgba(255,255,255,0.2);
color: var(--text-white);
font-size: var(--font-size-lg);
cursor: pointer;
transition: all var(--transition-fast);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.control-btn:hover {
background: rgba(255,255,255,0.3);
transform: scale(1.1);
}
.control-btn:active {
transform: scale(0.95);
}
.control-btn.active {
background: var(--primary);
box-shadow: 0 0 0 3px rgba(10, 124, 255, 0.3);
}
.control-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
/* AR Annotation Tools - PRD Requirements */
.ar-annotation-tools {
position: absolute;
left: var(--spacing-lg);
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
z-index: 5;
}
.tool-btn {
width: 40px;
height: 40px;
border-radius: var(--radius-full);
background: rgba(255,255,255,0.9);
border: 2px solid transparent;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-base);
transition: all var(--transition-fast);
}
.tool-btn:hover {
background: var(--text-white);
transform: scale(1.1);
}
.tool-btn.active {
border-color: var(--primary);
background: var(--primary);
color: var(--text-white);
}
/* Color Picker for AR Annotations */
.color-picker {
position: absolute;
left: 80px;
top: 50%;
transform: translateY(-50%);
background: rgba(255,255,255,0.95);
border-radius: var(--radius-md);
padding: var(--spacing-sm);
display: none;
flex-direction: column;
gap: var(--spacing-xs);
box-shadow: var(--shadow-lg);
z-index: 6;
}
.color-picker.show {
display: flex;
}
.color-option {
width: 24px;
height: 24px;
border-radius: var(--radius-full);
border: 2px solid transparent;
cursor: pointer;
transition: all var(--transition-fast);
}
.color-option:hover {
transform: scale(1.2);
border-color: var(--text-primary);
}
.color-option.selected {
border-color: var(--text-primary);
box-shadow: 0 0 0 2px var(--text-white);
}
.bottom-controls {
position: absolute;
bottom: 40px;
left: 20px;
right: 20px;
height: 80px;
background: rgba(0,0,0,0.7);
border-radius: 40px;
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
pointer-events: all;
}
.control-btn {
width: 48px;
height: 48px;
border-radius: 50%;
border: none;
background: rgba(255,255,255,0.2);
color: white;
font-size: 20px;
cursor: pointer;
transition: background 0.3s;
}
.control-btn:hover {
background: rgba(255,255,255,0.3);
}
.control-btn.active {
background: var(--primary);
}
/* Material Design Components */
/* Material Design Elevation */
.elevation-1 { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
.elevation-2 { box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); }
.elevation-3 { box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); }
.elevation-4 { box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); }
.elevation-5 { box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22); }
/* Material Design Motion */
.transition-enter {
animation: material-enter 0.3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.transition-exit {
animation: material-exit 0.2s cubic-bezier(0.4, 0.0, 1, 1);
}
@keyframes material-enter {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes material-exit {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.8);
}
}
/* Floating Action Button (FAB) */
.fab {
position: fixed;
bottom: 100px;
right: var(--spacing-lg);
width: 56px;
height: 56px;
border-radius: var(--radius-full);
background: var(--primary);
color: var(--text-white);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-lg);
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
transition: all var(--transition-fast);
z-index: var(--z-fixed);
}
.fab:hover {
background: var(--primary-dark);
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(0,0,0,0.4);
}
.fab:active {
transform: scale(0.95);
}
.fab:focus {
outline: 2px solid var(--primary-light);
outline-offset: 2px;
}
/* Extended FAB */
.fab-extended {
width: auto;
padding: 0 var(--spacing-lg);
border-radius: 28px;
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: var(--font-size-sm);
font-weight: 500;
}
/* Material Cards */
.material-card {
background: var(--bg-primary);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
box-shadow: var(--shadow-md);
transition: all var(--transition-fast);
position: relative;
overflow: hidden;
}
.material-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.material-card.ripple {
position: relative;
overflow: hidden;
}
/* Ripple Effect */
.ripple {
position: relative;
overflow: hidden;
}
.ripple::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: var(--radius-full);
background: rgba(255,255,255,0.5);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.ripple:active::before {
width: 300px;
height: 300px;
}
/* Material Text Fields */
.material-textfield {
position: relative;
margin-bottom: var(--spacing-lg);
}
.material-textfield input {
width: 100%;
padding: var(--spacing-lg) var(--spacing-lg) var(--spacing-sm);
border: 1px solid var(--border-primary);
border-radius: var(--radius-md);
background: transparent;
font-size: var(--font-size-base);
transition: all var(--transition-fast);
}
.material-textfield label {
position: absolute;
left: var(--spacing-lg);
top: 50%;
transform: translateY(-50%);
color: var(--text-secondary);
font-size: var(--font-size-base);
transition: all var(--transition-fast);
pointer-events: none;
}
.material-textfield input:focus ~ label,
.material-textfield input:not(:placeholder-shown) ~ label {
top: var(--spacing-sm);
font-size: var(--font-size-xs);
color: var(--primary);
}
.material-textfield input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(10, 124, 255, 0.2);
}
/* Ripple Effect for Material Design */
.ripple-effect {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
transform: scale(0);
animation: ripple-animation 0.6s linear;
pointer-events: none;
}
@keyframes ripple-animation {
to {
transform: scale(4);
opacity: 0;
}
}
/* AR Annotations - 3D Space Anchoring */
.ar-annotation {
position: absolute;
background: var(--accent);
color: var(--text-primary);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-xl);
font-size: var(--font-size-sm);
font-weight: 600;
animation: pulse 2s infinite;
pointer-events: none;
z-index: 3;
box-shadow: var(--shadow-md);
backdrop-filter: blur(5px);
}
/* AR Laser Pointer - PRD Requirement */
.laser-pointer {
position: absolute;
width: 20px;
height: 20px;
background: radial-gradient(circle, var(--danger) 0%, transparent 70%);
border-radius: var(--radius-full);
pointer-events: none;
z-index: 4;
animation: laser-pulse 1s infinite;
display: none;
}
.laser-pointer.active {
display: block;
}
@keyframes laser-pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.3);
opacity: 0.7;
}
}
/* AR Annotation Positions */
.annotation-1 { top: 200px; left: 50px; }
.annotation-2 { top: 400px; right: 60px; }
.annotation-3 { bottom: 200px; left: 80px; }
/* History Screen */
.history-screen {
flex: 1;
padding: 20px;
padding-bottom: 100px;
}
.search-bar {
width: 100%;
height: 40px;
padding: 0 16px;
border: 1px solid #e0e0e0;
border-radius: 20px;
background: #f8f9fa;
font-size: 14px;
margin-bottom: 24px;
}
.history-item {
background: white;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.history-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.history-title {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.history-date {
font-size: 12px;
color: var(--text-secondary);
}
.participants {
display: flex;
gap: 8px;
margin: 12px 0;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--primary);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: 600;
}
.history-actions {
display: flex;
gap: 12px;
margin-top: 12px;
}
.action-btn {
flex: 1;
height: 36px;
border: 1px solid var(--primary);
background: white;
color: var(--primary);
border-radius: 6px;
font-size: 14px;
cursor: pointer;
}
.action-btn.primary {
background: var(--primary);
color: white;
}
/* Contact Screen */
.contact-screen {
flex: 1;
padding: 20px;
padding-bottom: 100px;
}
.org-tree {
margin-top: 16px;
}
.org-item {
background: white;
border-radius: 8px;
margin-bottom: 8px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.org-header {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
border-left: 4px solid var(--primary);
}
.org-name {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.member-count {
font-size: 12px;
color: var(--text-secondary);
}
.members-list {
display: none;
background: var(--secondary);
padding: 8px 0;
}
.members-list.expanded {
display: block;
}
.member-item {
padding: 12px 20px;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: background 0.2s;
}
.member-item:hover {
background: rgba(10, 124, 255, 0.1);
}
.member-info {
flex: 1;
}
.member-name {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
}
.member-role {
font-size: 12px;
color: var(--text-secondary);
}
/* Profile Screen */
.profile-screen {
flex: 1;
padding: 20px;
padding-bottom: 100px;
}
.profile-header {
text-align: center;
padding: 40px 0;
border-bottom: 1px solid #e0e0e0;
margin-bottom: 32px;
}
.profile-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
background: var(--primary);
margin: 0 auto 16px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 32px;
font-weight: 600;
}
.profile-name {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
}
.profile-role {
font-size: 14px;
color: var(--text-secondary);
}
.profile-menu {
list-style: none;
}
.menu-item {
background: white;
border-radius: 8px;
margin-bottom: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.menu-link {
display: block;
padding: 16px;
color: var(--text-primary);
text-decoration: none;
font-size: 16px;
}
.logout-btn {
width: 100%;
height: 48px;
background: white;
color: var(--danger);
border: 2px solid var(--danger);
border-radius: 8px;
font-size: 16px;
margin-top: 32px;
cursor: pointer;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--text-secondary);
}
.empty-icon {
width: 80px;
height: 80px;
background: #f0f0f0;
border-radius: 50%;
margin: 0 auto 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
}
.empty-text {
font-size: 16px;
margin-bottom: 20px;
}
.empty-action {
background: var(--primary);
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
}
</style>
</head>
<body>
<!-- Main Application Container -->
<div class="phone-container" role="application" aria-label="XSynergy AR远程协作应用">
<!-- Login Screen -->
<div class="screen active" id="login" role="main" aria-label="登录页面">
<div class="status-bar" role="status" aria-live="polite">
<span aria-label="当前时间">9:41</span>
<span aria-label="电池电量">100%</span>
</div>
<form class="login-screen" role="form" aria-label="登录表单">
<div class="logo" role="heading" aria-level="1">XSynergy</div>
<div class="material-textfield">
<input type="tel" class="input-field" placeholder=" " id="phone" aria-label="手机号" required autocomplete="tel">
<label for="phone">手机号</label>
</div>
<div class="input-group">
<div class="material-textfield code-input-group">
<input type="text" class="input-field code-input" placeholder=" " id="code" aria-label="验证码" required autocomplete="one-time-code" inputmode="numeric" pattern="\d{6}">
<label for="code">验证码</label>
</div>
<button type="button" class="code-btn" aria-label="获取验证码" title="获取验证码">获取验证码</button>
</div>
<button type="submit" class="primary-btn" aria-label="登录系统">登录</button>
<div class="checkbox-group">
<input type="checkbox" id="remember" name="remember" aria-describedby="remember-help">
<label for="remember">记住我</label>
<span id="remember-help" class="sr-only">在设备上保持登录状态</span>
</div>
<nav class="links" role="navigation" aria-label="登录选项">
<a href="#" class="link" aria-label="忘记密码">忘记密码</a>
<a href="#" class="link" aria-label="企业SSO登录">SSO登录</a>
</nav>
</form>
</div>
<!-- Dashboard Screen -->
<div class="screen" id="dashboard" role="main" aria-label="主页面">
<div class="status-bar" role="status" aria-live="polite">
<span aria-label="当前时间">9:41</span>
<span aria-label="电池电量">100%</span>
</div>
<div class="dashboard">
<h1 class="greeting" role="heading" aria-level="1">你好,李明</h1>
<section class="action-section" aria-labelledby="actions-heading">
<h2 id="actions-heading" class="sr-only">快速操作</h2>
<button class="large-btn ripple" onclick="switchScreen('ar-session')" aria-label="发起新的AR协作会话">
发起协作
</button>
<button class="secondary-btn ripple" aria-label="加入现有协作会话">
加入协作
</button>
<button class="text-btn ripple" aria-label="预约未来的协作会话">
预约协作
</button>
</section>
<section aria-labelledby="meetings-heading">
<h2 id="meetings-heading" class="section-title">今日预约</h2>
<article class="meeting-card ripple" role="article" tabindex="0" aria-label="设备检修会议14:30开始">
<div class="status-badge" role="status" aria-live="polite">即将开始</div>
<h3 class="meeting-title">设备检修会议</h3>
<div class="meeting-meta">
<time datetime="14:30">14:30</time> - <time datetime="15:30">15:30</time> |
<span role="text">发起人:王工</span>
</div>
<button class="join-btn ripple" aria-label="立即加入设备检修会议">
立即加入
</button>
</article>
<article class="meeting-card ripple" role="article" tabindex="0" aria-label="项目评审会议16:00开始">
<h3 class="meeting-title">项目评审会议</h3>
<div class="meeting-meta">
<time datetime="16:00">16:00</time> - <time datetime="17:00">17:00</time> |
<span role="text">发起人:张总</span>
</div>
</article>
</section>
</div>
<nav class="bottom-tabs" role="navigation" aria-label="主导航">
<div class="tab-item active" onclick="switchTab('dashboard')" role="tab" aria-selected="true" tabindex="0" aria-label="首页">
<div class="tab-icon home" aria-hidden="true"></div>
<span class="tab-label">首页</span>
</div>
<div class="tab-item" onclick="switchTab('contacts')" role="tab" aria-selected="false" tabindex="0" aria-label="通讯录">
<div class="tab-icon contacts" aria-hidden="true"></div>
<span class="tab-label">通讯录</span>
</div>
<div class="tab-item" onclick="switchTab('history')" role="tab" aria-selected="false" tabindex="0" aria-label="历史记录">
<div class="tab-icon history" aria-hidden="true"></div>
<span class="tab-label">历史</span>
</div>
<div class="tab-item" onclick="switchTab('profile')" role="tab" aria-selected="false" tabindex="0" aria-label="个人资料">
<div class="tab-icon profile" aria-hidden="true"></div>
<span class="tab-label">我的</span>
</div>
</nav>
</div>
<!-- AR Session Screen -->
<div class="screen" id="ar-session" role="main" aria-label="AR协作会话">
<div class="status-bar" role="status" aria-live="polite">
<span aria-label="当前时间">9:41</span>
<span aria-label="电池电量">100%</span>
</div>
<div class="ar-session">
<div class="camera-view" aria-label="摄像头视图" role="img"></div>
<div class="ar-overlay" aria-label="AR标注层">
<div class="ar-annotation annotation-1" role="note" aria-label="检查这里">检查这里</div>
<div class="ar-annotation annotation-2" role="note" aria-label="注意温度">注意温度</div>
<div class="ar-annotation annotation-3" role="note" aria-label="更换部件">更换部件</div>
</div>
<!-- AR Annotation Tools - PRD Requirements -->
<div class="ar-annotation-tools" role="toolbar" aria-label="AR标注工具">
<button class="tool-btn" data-tool="arrow" title="箭头工具" aria-label="箭头工具"></button>
<button class="tool-btn" data-tool="pen" title="画笔工具" aria-label="画笔工具">✏️</button>
<button class="tool-btn" data-tool="rectangle" title="矩形工具" aria-label="矩形工具"></button>
<button class="tool-btn" data-tool="laser" title="激光笔" aria-label="激光笔">🔴</button>
<button class="tool-btn" data-tool="clear" title="清除标注" aria-label="清除标注">🗑️</button>
</div>
<!-- Color Picker for Annotations -->
<div class="color-picker" role="group" aria-label="标注颜色选择">
<div class="color-option selected" style="background-color: #FFD700;" data-color="#FFD700" title="黄色" aria-label="黄色"></div>
<div class="color-option" style="background-color: #FF4D4F;" data-color="#FF4D4F" title="红色" aria-label="红色"></div>
<div class="color-option" style="background-color: #52C41A;" data-color="#52C41A" title="绿色" aria-label="绿色"></div>
<div class="color-option" style="background-color: #0A7CFF;" data-color="#0A7CFF" title="蓝色" aria-label="蓝色"></div>
<div class="color-option" style="background-color: #FFFFFF;" data-color="#FFFFFF" title="白色" aria-label="白色"></div>
</div>
<!-- Laser Pointer -->
<div class="laser-pointer" aria-label="激光笔指针"></div>
<div class="top-bar">
<div class="session-info">
<div class="timer" role="timer" aria-label="会话时长">15:32</div>
<div class="network-status" role="status" aria-label="网络状态良好" title="网络状态良好"></div>
</div>
<button class="hangup-btn" onclick="switchScreen('dashboard')" aria-label="结束会话" title="结束会话"></button>
</div>
<div class="video-pip" role="button" aria-label="远程专家视频" title="远程专家视频" draggable="true">
<div class="video-placeholder" aria-label="视频预览"></div>
</div>
<div class="bottom-controls" role="toolbar" aria-label="会话控制">
<button class="control-btn" aria-label="麦克风" title="麦克风" aria-pressed="true">🎤</button>
<button class="control-btn" aria-label="摄像头" title="摄像头" aria-pressed="true">📹</button>
<button class="control-btn active" aria-label="标注工具" title="标注工具" aria-pressed="true">✏️</button>
<button class="control-btn" aria-label="屏幕共享" title="屏幕共享">📤</button>
<button class="control-btn" aria-label="更多选项" title="更多选项"></button>
</div>
</div>
</div>
<!-- History Screen -->
<div class="screen" id="history">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="history-screen">
<input type="text" class="search-bar" placeholder="搜索会议主题或参会人">
<div class="history-item">
<div class="history-header">
<div class="history-title">设备检修指导</div>
<div class="history-date">今天 14:30</div>
</div>
<div class="participants">
<div class="avatar"></div>
<div class="avatar"></div>
<div class="avatar"></div>
</div>
<div class="history-actions">
<button class="action-btn">回放</button>
<button class="action-btn primary">查看纪要</button>
</div>
</div>
<div class="history-item">
<div class="history-header">
<div class="history-title">新员工培训</div>
<div class="history-date">昨天 09:15</div>
</div>
<div class="participants">
<div class="avatar"></div>
<div class="avatar"></div>
</div>
<div class="history-actions">
<button class="action-btn">回放</button>
<button class="action-btn">查看纪要</button>
</div>
</div>
<div class="history-item">
<div class="history-header">
<div class="history-title">安全检查会议</div>
<div class="history-date">2天前 16:00</div>
</div>
<div class="participants">
<div class="avatar"></div>
<div class="avatar"></div>
<div class="avatar"></div>
<div class="avatar">+2</div>
</div>
<div class="history-actions">
<button class="action-btn">回放</button>
<button class="action-btn">查看纪要</button>
</div>
</div>
</div>
<div class="bottom-tabs">
<div class="tab-item" onclick="switchTab('dashboard')">
<div class="tab-icon home"></div>
<span class="tab-label">首页</span>
</div>
<div class="tab-item" onclick="switchTab('contacts')">
<div class="tab-icon contacts"></div>
<span class="tab-label">通讯录</span>
</div>
<div class="tab-item active" onclick="switchTab('history')">
<div class="tab-icon history"></div>
<span class="tab-label">历史</span>
</div>
<div class="tab-item" onclick="switchTab('profile')">
<div class="tab-icon profile"></div>
<span class="tab-label">我的</span>
</div>
</div>
</div>
<!-- Contacts Screen -->
<div class="screen" id="contacts">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="contact-screen">
<input type="text" class="search-bar" placeholder="搜索姓名/部门/职位">
<div class="org-tree">
<div class="org-item">
<div class="org-header" onclick="toggleOrgItem(this)">
<div class="org-name">技术部</div>
<div class="member-count">12人 </div>
</div>
<div class="members-list">
<div class="member-item">
<div class="avatar"></div>
<div class="member-info">
<div class="member-name">李明</div>
<div class="member-role">高级工程师</div>
</div>
</div>
<div class="member-item">
<div class="avatar"></div>
<div class="member-info">
<div class="member-name">王强</div>
<div class="member-role">技术专家</div>
</div>
</div>
<div class="member-item">
<div class="avatar"></div>
<div class="member-info">
<div class="member-name">张伟</div>
<div class="member-role">工程师</div>
</div>
</div>
</div>
</div>
<div class="org-item">
<div class="org-header" onclick="toggleOrgItem(this)">
<div class="org-name">运营部</div>
<div class="member-count">8人 </div>
</div>
<div class="members-list">
<div class="member-item">
<div class="avatar"></div>
<div class="member-info">
<div class="member-name">陈静</div>
<div class="member-role">运营经理</div>
</div>
</div>
<div class="member-item">
<div class="avatar"></div>
<div class="member-info">
<div class="member-name">赵磊</div>
<div class="member-role">运营专员</div>
</div>
</div>
</div>
</div>
<div class="org-item">
<div class="org-header" onclick="toggleOrgItem(this)">
<div class="org-name">销售部</div>
<div class="member-count">15人 </div>
</div>
<div class="members-list">
<div class="member-item">
<div class="avatar"></div>
<div class="member-info">
<div class="member-name">孙丽</div>
<div class="member-role">销售总监</div>
</div>
</div>
<div class="member-item">
<div class="avatar"></div>
<div class="member-info">
<div class="member-name">周华</div>
<div class="member-role">销售经理</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="bottom-tabs">
<div class="tab-item" onclick="switchTab('dashboard')">
<div class="tab-icon home"></div>
<span class="tab-label">首页</span>
</div>
<div class="tab-item active" onclick="switchTab('contacts')">
<div class="tab-icon contacts"></div>
<span class="tab-label">通讯录</span>
</div>
<div class="tab-item" onclick="switchTab('history')">
<div class="tab-icon history"></div>
<span class="tab-label">历史</span>
</div>
<div class="tab-item" onclick="switchTab('profile')">
<div class="tab-icon profile"></div>
<span class="tab-label">我的</span>
</div>
</div>
</div>
<!-- Profile Screen -->
<div class="screen" id="profile">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="profile-screen">
<div class="profile-header">
<div class="profile-avatar"></div>
<div class="profile-name">李明</div>
<div class="profile-role">技术部 · 高级工程师</div>
</div>
<ul class="profile-menu">
<li class="menu-item">
<a href="#" class="menu-link">个人资料</a>
</li>
<li class="menu-item">
<a href="#" class="menu-link">设置</a>
</li>
<li class="menu-item">
<a href="#" class="menu-link">帮助与反馈</a>
</li>
<li class="menu-item">
<a href="#" class="menu-link">关于我们</a>
</li>
</ul>
<button class="logout-btn" onclick="switchScreen('login')">退出登录</button>
</div>
<div class="bottom-tabs">
<div class="tab-item" onclick="switchTab('dashboard')">
<div class="tab-icon home"></div>
<span class="tab-label">首页</span>
</div>
<div class="tab-item" onclick="switchTab('contacts')">
<div class="tab-icon contacts"></div>
<span class="tab-label">通讯录</span>
</div>
<div class="tab-item" onclick="switchTab('history')">
<div class="tab-icon history"></div>
<span class="tab-label">历史</span>
</div>
<div class="tab-item active" onclick="switchTab('profile')">
<div class="tab-icon profile"></div>
<span class="tab-label">我的</span>
</div>
</div>
</div>
<!-- Empty State Example -->
<div class="screen" id="empty">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="empty-state">
<div class="empty-icon">📅</div>
<div class="empty-text">暂无预约会议,发起一个吧!</div>
<button class="empty-action">发起协作</button>
</div>
</div>
</div>
<!-- Navigation Controls -->
<nav class="nav-controls" role="navigation" aria-label="页面导航">
<button class="nav-btn active ripple" onclick="switchScreen('login')" aria-label="登录页面" aria-pressed="true">登录</button>
<button class="nav-btn ripple" onclick="switchScreen('dashboard')" aria-label="主页面" aria-pressed="false">主页</button>
<button class="nav-btn ripple" onclick="switchScreen('ar-session')" aria-label="AR协作会话" aria-pressed="false">AR协作</button>
<button class="nav-btn ripple" onclick="switchScreen('history')" aria-label="历史记录" aria-pressed="false">历史</button>
<button class="nav-btn ripple" onclick="switchScreen('contacts')" aria-label="通讯录" aria-pressed="false">通讯录</button>
<button class="nav-btn ripple" onclick="switchScreen('profile')" aria-label="个人资料" aria-pressed="false">我的</button>
</nav>
<!-- Screen Reader Announcements -->
<div id="screen-reader-announcements" class="sr-only" aria-live="polite" aria-atomic="true"></div>
<!-- Loading Indicator -->
<div id="global-loading" class="loading" style="display: none;" aria-label="加载中"></div>
<script>
// Accessibility and Performance Optimized JavaScript
// State Management
const AppState = {
currentScreen: 'login',
currentTab: 'dashboard',
isARSessionActive: false,
networkStatus: 'good',
annotations: [],
userData: {
name: '李明',
role: '技术部 · 高级工程师',
avatar: '李'
},
sessionData: {
duration: 0,
participants: [],
isRecording: false
}
};
// Performance Optimization: Debounce and Throttle
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// Accessibility: Announce Screen Changes
function announceToScreenReader(message) {
const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'polite');
announcement.setAttribute('aria-atomic', 'true');
announcement.className = 'sr-only';
announcement.textContent = message;
document.body.appendChild(announcement);
setTimeout(() => {
document.body.removeChild(announcement);
}, 1000);
}
// Enhanced Screen Switching with Transitions
function switchScreen(screenId) {
// Validate screen ID
const validScreens = ['login', 'dashboard', 'ar-session', 'history', 'contacts', 'profile'];
if (!validScreens.includes(screenId)) {
console.error(`Invalid screen ID: ${screenId}`);
return;
}
// Add transition class to prevent interactions
document.body.classList.add('screen-transitioning');
// Hide all screens with animation
document.querySelectorAll('.screen').forEach(screen => {
screen.classList.remove('active');
screen.style.opacity = '0';
screen.style.transform = 'translateY(20px)';
});
// Show target screen with animation
const targetScreen = document.getElementById(screenId);
if (targetScreen) {
setTimeout(() => {
targetScreen.classList.add('active');
targetScreen.style.opacity = '1';
targetScreen.style.transform = 'translateY(0)';
AppState.currentScreen = screenId;
// Announce screen change for accessibility
const screenNames = {
'login': '登录页面',
'dashboard': '主页面',
'ar-session': 'AR协作会话',
'history': '历史记录',
'contacts': '通讯录',
'profile': '个人资料'
};
announceToScreenReader(`已切换到${screenNames[screenId]}`);
// Update nav buttons
updateNavButtons(screenId);
// Remove transition class
document.body.classList.remove('screen-transitioning');
// Initialize screen-specific features
initializeScreenFeatures(screenId);
}, 150);
}
}
// Update Navigation Buttons
function updateNavButtons(activeScreenId) {
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.classList.remove('active');
btn.setAttribute('aria-pressed', 'false');
});
const activeBtn = document.querySelector(`[onclick="switchScreen('${activeScreenId}')"]`);
if (activeBtn) {
activeBtn.classList.add('active');
activeBtn.setAttribute('aria-pressed', 'true');
}
}
// Initialize Screen-Specific Features
function initializeScreenFeatures(screenId) {
switch(screenId) {
case 'ar-session':
initializeARSession();
break;
case 'dashboard':
initializeDashboard();
break;
case 'contacts':
initializeContacts();
break;
case 'history':
initializeHistory();
break;
}
}
// AR Session Initialization
function initializeARSession() {
AppState.isARSessionActive = true;
// Start session timer
startSessionTimer();
// Initialize AR annotations
initializeARAnnotations();
// Setup network monitoring
setupNetworkMonitoring();
// Auto-hide controls after 5 seconds
setupAutoHideControls();
}
// Session Timer
function startSessionTimer() {
AppState.sessionData.duration = 0;
const timer = document.querySelector('.timer');
if (timer) {
const timerInterval = setInterval(() => {
if (!AppState.isARSessionActive) {
clearInterval(timerInterval);
return;
}
AppState.sessionData.duration++;
const minutes = Math.floor(AppState.sessionData.duration / 60);
const seconds = AppState.sessionData.duration % 60;
timer.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}, 1000);
}
}
// AR Annotations System
function initializeARAnnotations() {
const annotationTools = document.querySelector('.ar-annotation-tools');
const colorPicker = document.querySelector('.color-picker');
if (annotationTools) {
// Tool selection
annotationTools.addEventListener('click', (e) => {
if (e.target.classList.contains('tool-btn')) {
// Remove active class from all tools
document.querySelectorAll('.tool-btn').forEach(btn => {
btn.classList.remove('active');
});
// Add active class to clicked tool
e.target.classList.add('active');
// Show/hide color picker for annotation tools
if (e.target.dataset.tool === 'annotation') {
colorPicker.classList.add('show');
} else {
colorPicker.classList.remove('show');
}
// Announce tool selection
announceToScreenReader(`已选择${e.target.title || '工具'}`);
}
});
}
}
// Network Monitoring
function setupNetworkMonitoring() {
const networkStatus = document.querySelector('.network-status');
if (!networkStatus) return;
// Simulate network status changes
const networkStates = ['good', 'weak', 'offline'];
let currentIndex = 0;
const networkInterval = setInterval(() => {
if (!AppState.isARSessionActive) {
clearInterval(networkInterval);
return;
}
// Cycle through network states for demo
currentIndex = (currentIndex + 1) % networkStates.length;
const status = networkStates[currentIndex];
networkStatus.className = `network-status ${status}`;
AppState.networkStatus = status;
// Announce network status changes
const statusMessages = {
'good': '网络连接良好',
'weak': '网络连接较弱',
'offline': '网络连接已断开'
};
announceToScreenReader(statusMessages[status]);
}, 10000); // Check every 10 seconds
}
// Auto-hide Controls
function setupAutoHideControls() {
const controls = document.querySelector('.bottom-controls');
const arSession = document.getElementById('ar-session');
if (!controls || !arSession) return;
let hideTimeout;
function showControls() {
controls.classList.remove('collapsed');
clearTimeout(hideTimeout);
hideTimeout = setTimeout(() => {
controls.classList.add('collapsed');
}, 5000);
}
arSession.addEventListener('click', showControls);
arSession.addEventListener('touchstart', showControls);
// Show controls initially
showControls();
}
// Optimized Tab Switching
function switchTab(tabId) {
switchScreen(tabId);
AppState.currentTab = tabId;
// Update tab bar for the current screen
const currentScreenElement = document.getElementById(tabId);
if (currentScreenElement) {
const tabItems = currentScreenElement.querySelectorAll('.tab-item');
tabItems.forEach(item => {
item.classList.remove('active');
item.setAttribute('aria-selected', 'false');
});
const targetTab = currentScreenElement.querySelector(`[onclick="switchTab('${tabId}')"]`);
if (targetTab) {
targetTab.classList.add('active');
targetTab.setAttribute('aria-selected', 'true');
}
}
}
// Optimized Organization Tree Toggle
function toggleOrgItem(header) {
const membersList = header.nextElementSibling;
const isExpanded = membersList.classList.contains('expanded');
const memberCount = header.querySelector('.member-count');
// Close all other items
document.querySelectorAll('.members-list').forEach(list => {
list.classList.remove('expanded');
list.setAttribute('aria-expanded', 'false');
});
document.querySelectorAll('.org-header').forEach(h => {
h.setAttribute('aria-expanded', 'false');
});
// Toggle current item
if (!isExpanded) {
membersList.classList.add('expanded');
membersList.setAttribute('aria-expanded', 'true');
header.setAttribute('aria-expanded', 'true');
memberCount.innerHTML = memberCount.innerHTML.replace('', '⌄');
// Announce expansion
const orgName = header.querySelector('.org-name').textContent;
announceToScreenReader(`已展开${orgName}部门`);
} else {
header.setAttribute('aria-expanded', 'false');
memberCount.innerHTML = memberCount.innerHTML.replace('⌄', '');
}
}
// Keyboard Navigation Support
document.addEventListener('keydown', (e) => {
// Tab navigation between screens
if (e.key === 'Tab' && e.ctrlKey) {
e.preventDefault();
const screens = ['login', 'dashboard', 'ar-session', 'history', 'contacts', 'profile'];
const currentIndex = screens.indexOf(AppState.currentScreen);
const nextIndex = (currentIndex + 1) % screens.length;
switchScreen(screens[nextIndex]);
}
// Escape key to exit AR session
if (e.key === 'Escape' && AppState.currentScreen === 'ar-session') {
switchScreen('dashboard');
}
// Space/Enter to activate buttons
if ((e.key === ' ' || e.key === 'Enter') && document.activeElement.tagName === 'BUTTON') {
document.activeElement.click();
}
});
// Touch and Click Interactions
document.addEventListener('DOMContentLoaded', function() {
// Add ripple effects to buttons
document.querySelectorAll('button, .tab-item, .member-item, .meeting-card').forEach(element => {
element.classList.add('ripple');
element.addEventListener('click', function(e) {
// Remove previous ripples
const prevRipples = this.querySelectorAll('.ripple-effect');
prevRipples.forEach(ripple => ripple.remove());
// Create new ripple
const ripple = document.createElement('span');
ripple.className = 'ripple-effect';
const rect = this.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const x = e.clientX - rect.left - size / 2;
const y = e.clientY - rect.top - size / 2;
ripple.style.width = ripple.style.height = size + 'px';
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
this.appendChild(ripple);
// Remove ripple after animation
setTimeout(() => ripple.remove(), 600);
});
});
// Initialize first screen
switchScreen('login');
// Setup form validation
setupFormValidation();
// Setup lazy loading for images
setupLazyLoading();
});
// Form Validation
function setupFormValidation() {
const inputs = document.querySelectorAll('.input-field');
inputs.forEach(input => {
input.addEventListener('blur', function() {
validateInput(this);
});
input.addEventListener('input', debounce(function() {
validateInput(this);
}, 300));
});
}
function validateInput(input) {
const value = input.value.trim();
const type = input.type;
// Remove previous validation classes
input.classList.remove('error', 'success');
// Validate based on input type
let isValid = true;
if (type === 'tel') {
isValid = /^1[3-9]\d{9}$/.test(value);
} else if (type === 'text' && input.placeholder.includes('验证码')) {
isValid = /^\d{6}$/.test(value);
}
// Add validation classes
if (value && !isValid) {
input.classList.add('error');
announceToScreenReader('输入格式不正确');
} else if (value && isValid) {
input.classList.add('success');
}
}
// Lazy Loading
function setupLazyLoading() {
// Intersection Observer for lazy loading elements
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('loaded');
observer.unobserve(entry.target);
}
});
});
// Observe elements that need lazy loading
document.querySelectorAll('.meeting-card, .history-item, .member-item').forEach(el => {
observer.observe(el);
});
}
// Screen Initialization Functions
function initializeDashboard() {
// Update user greeting
const greeting = document.querySelector('.greeting');
if (greeting) {
greeting.textContent = `你好,${AppState.userData.name}`;
}
}
function initializeContacts() {
// Setup search functionality
const searchInput = document.querySelector('.contact-screen .search-bar');
if (searchInput) {
searchInput.addEventListener('input', debounce(function() {
filterContacts(this.value);
}, 300));
}
}
function initializeHistory() {
// Setup search functionality
const searchInput = document.querySelector('.history-screen .search-bar');
if (searchInput) {
searchInput.addEventListener('input', debounce(function() {
filterHistory(this.value);
}, 300));
}
}
function filterContacts(query) {
const memberItems = document.querySelectorAll('.member-item');
const searchLower = query.toLowerCase();
memberItems.forEach(item => {
const name = item.querySelector('.member-name').textContent.toLowerCase();
const role = item.querySelector('.member-role').textContent.toLowerCase();
if (name.includes(searchLower) || role.includes(searchLower)) {
item.style.display = 'flex';
item.setAttribute('aria-hidden', 'false');
} else {
item.style.display = 'none';
item.setAttribute('aria-hidden', 'true');
}
});
}
function filterHistory(query) {
const historyItems = document.querySelectorAll('.history-item');
const searchLower = query.toLowerCase();
historyItems.forEach(item => {
const title = item.querySelector('.history-title').textContent.toLowerCase();
if (title.includes(searchLower)) {
item.style.display = 'block';
item.setAttribute('aria-hidden', 'false');
} else {
item.style.display = 'none';
item.setAttribute('aria-hidden', 'true');
}
});
}
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
AppState.isARSessionActive = false;
// Cleanup timers and intervals
if (window.sessionTimer) {
clearInterval(window.sessionTimer);
}
if (window.networkMonitor) {
clearInterval(window.networkMonitor);
}
});
// Comprehensive Error Handling
window.addEventListener('error', (e) => {
console.error('Application error:', e.error);
announceToScreenReader('应用出现错误,请刷新页面重试');
// Log error for debugging
if (window.errorLog) {
window.errorLog.push({
timestamp: new Date().toISOString(),
message: e.error.message,
stack: e.error.stack
});
}
});
// Unhandled Promise Rejections
window.addEventListener('unhandledrejection', (e) => {
console.error('Unhandled promise rejection:', e.reason);
announceToScreenReader('操作失败,请重试');
});
// Network Status Monitoring with Fallback
if ('onLine' in navigator) {
window.addEventListener('online', () => {
AppState.networkStatus = 'good';
announceToScreenReader('网络连接已恢复');
// Retry failed operations
retryFailedOperations();
});
window.addEventListener('offline', () => {
AppState.networkStatus = 'offline';
announceToScreenReader('网络连接已断开');
// Cache current state for offline mode
cacheApplicationState();
});
}
// Performance Monitoring
if ('performance' in window) {
window.addEventListener('load', () => {
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
console.log(`Page load time: ${loadTime}ms`);
if (loadTime > 3000) {
console.warn('Page load time exceeds 3 seconds');
}
});
}
// Memory Management
function cleanupMemory() {
// Clear large objects
if (AppState.annotations.length > 100) {
AppState.annotations = AppState.annotations.slice(-50);
}
// Clear DOM elements
const ripples = document.querySelectorAll('.ripple-effect');
ripples.forEach(ripple => ripple.remove());
}
// Run cleanup every 30 seconds
setInterval(cleanupMemory, 30000);
// Retry Failed Operations
function retryFailedOperations() {
// Implement retry logic for failed API calls
console.log('Retrying failed operations...');
}
// Cache Application State
function cacheApplicationState() {
try {
const stateToCache = {
userData: AppState.userData,
sessionData: AppState.sessionData,
timestamp: new Date().toISOString()
};
localStorage.setItem('appState', JSON.stringify(stateToCache));
} catch (e) {
console.warn('Failed to cache application state:', e);
}
}
// Restore Application State
function restoreApplicationState() {
try {
const cachedState = localStorage.getItem('appState');
if (cachedState) {
const state = JSON.parse(cachedState);
// Restore relevant state
AppState.userData = state.userData || AppState.userData;
AppState.sessionData = state.sessionData || AppState.sessionData;
}
} catch (e) {
console.warn('Failed to restore application state:', e);
}
}
// Initialize on load
window.addEventListener('load', () => {
restoreApplicationState();
});
// Export functions for global access
window.switchScreen = switchScreen;
window.switchTab = switchTab;
window.toggleOrgItem = toggleOrgItem;
window.AppState = AppState; // Export for debugging
// Error logging array
window.errorLog = [];
</script>
</body>