Files
xsynergy-android/docs/ui.html
2025-09-17 22:50:29 +08:00

4497 lines
155 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
/* Settings Screen Styles */
.settings-group {
margin-bottom: var(--spacing-xxxl);
}
.settings-subtitle {
font-size: var(--font-size-base);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
padding-left: var(--spacing-sm);
border-left: 3px solid var(--primary);
}
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-md) 0;
border-bottom: 1px solid var(--border-primary);
}
.setting-label {
font-size: var(--font-size-base);
color: var(--text-primary);
}
.setting-select {
padding: var(--spacing-sm);
border: 1px solid var(--border-primary);
border-radius: var(--radius-sm);
background: var(--bg-primary);
}
.setting-slider {
width: 100px;
}
/* Switch Toggle */
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--text-tertiary);
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: var(--primary);
}
input:checked + .slider:before {
transform: translateX(26px);
}
/* AI Assistant Styles */
.ai-screen {
flex: 1;
padding: var(--spacing-lg);
display: flex;
flex-direction: column;
}
.ai-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--spacing-lg);
}
.back-btn {
background: none;
border: none;
font-size: var(--font-size-xl);
cursor: pointer;
color: var(--text-primary);
}
.voice-btn {
background: none;
border: none;
font-size: var(--font-size-lg);
cursor: pointer;
}
.chat-container {
flex: 1;
overflow-y: auto;
margin-bottom: var(--spacing-lg);
}
.message {
display: flex;
margin-bottom: var(--spacing-md);
align-items: flex-start;
}
.message-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
margin-right: var(--spacing-sm);
}
.message-content {
background: var(--secondary);
padding: var(--spacing-md);
border-radius: var(--radius-lg);
max-width: 70%;
}
.input-container {
display: flex;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-lg);
}
.ai-input {
flex: 1;
padding: var(--spacing-md);
border: 1px solid var(--border-primary);
border-radius: var(--radius-md);
}
.send-btn {
padding: var(--spacing-md) var(--spacing-lg);
background: var(--primary);
color: white;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
}
.quick-questions {
margin-top: var(--spacing-lg);
}
.question-chips {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.question-chip {
padding: var(--spacing-sm) var(--spacing-md);
background: var(--secondary);
border: 1px solid var(--primary-light);
border-radius: 20px;
font-size: var(--font-size-sm);
cursor: pointer;
transition: all var(--transition-fast);
}
.question-chip:hover {
background: var(--primary-light);
color: white;
}
/* Whiteboard Styles */
.whiteboard-container {
flex: 1;
display: flex;
flex-direction: column;
padding: var(--spacing-lg);
}
.whiteboard-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--spacing-lg);
}
.whiteboard-tools {
display: flex;
gap: var(--spacing-sm);
}
#whiteboard-canvas {
flex: 1;
border: 2px solid var(--border-primary);
border-radius: var(--radius-md);
background: white;
cursor: crosshair;
}
.color-palette {
display: flex;
gap: var(--spacing-sm);
margin: var(--spacing-md) 0;
}
.brush-size {
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.size-slider {
flex: 1;
}
/* File Sharing Styles */
.file-screen {
flex: 1;
padding: var(--spacing-lg);
}
.file-header {
display: flex;
align-items: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
}
.file-upload-area {
border: 2px dashed var(--border-primary);
border-radius: var(--radius-lg);
padding: var(--spacing-xxxxl);
text-align: center;
margin-bottom: var(--spacing-xl);
cursor: pointer;
transition: all var(--transition-fast);
}
.file-upload-area:hover {
border-color: var(--primary);
background: var(--secondary);
}
.upload-icon {
font-size: var(--font-size-xxxl);
margin-bottom: var(--spacing-md);
}
.upload-btn {
padding: var(--spacing-sm) var(--spacing-lg);
background: var(--primary);
color: white;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
margin-top: var(--spacing-md);
}
.file-list {
margin-bottom: var(--spacing-xl);
}
.file-item {
display: flex;
align-items: center;
padding: var(--spacing-md);
border: 1px solid var(--border-primary);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-sm);
background: var(--bg-primary);
}
.file-icon {
font-size: var(--font-size-xl);
margin-right: var(--spacing-md);
}
.file-info {
flex: 1;
}
.file-name {
font-weight: 600;
color: var(--text-primary);
}
.file-size {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.file-action {
padding: var(--spacing-sm) var(--spacing-md);
background: var(--primary);
color: white;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
}
.share-options {
margin-top: var(--spacing-xl);
}
.share-option {
margin-bottom: var(--spacing-md);
}
.participant-select {
width: 100%;
padding: var(--spacing-sm);
border: 1px solid var(--border-primary);
border-radius: var(--radius-sm);
margin-top: var(--spacing-sm);
}
/* Playback Styles */
.playback-screen {
flex: 1;
display: flex;
flex-direction: column;
}
.playback-header {
padding: var(--spacing-lg);
border-bottom: 1px solid var(--border-primary);
}
.playback-info {
display: flex;
gap: var(--spacing-lg);
color: var(--text-secondary);
font-size: var(--font-size-sm);
margin-top: var(--spacing-sm);
}
.video-player {
flex: 1;
position: relative;
background: black;
}
.playback-video {
width: 100%;
height: 100%;
object-fit: contain;
}
.playback-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0,0,0,0.8);
padding: var(--spacing-md);
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.progress-bar {
flex: 1;
}
.time-display {
color: white;
font-size: var(--font-size-sm);
}
.playback-sidebar {
width: 300px;
border-left: 1px solid var(--border-primary);
display: flex;
flex-direction: column;
}
.sidebar-tabs {
display: flex;
border-bottom: 1px solid var(--border-primary);
}
.tab-btn {
flex: 1;
padding: var(--spacing-md);
background: none;
border: none;
cursor: pointer;
border-bottom: 2px solid transparent;
}
.tab-btn.active {
border-bottom-color: var(--primary);
color: var(--primary);
}
.tab-content {
flex: 1;
padding: var(--spacing-md);
overflow-y: auto;
}
.annotation-item {
padding: var(--spacing-sm);
border-bottom: 1px solid var(--border-primary);
cursor: pointer;
}
.annotation-time {
color: var(--primary);
font-weight: 600;
margin-right: var(--spacing-sm);
}
.transcript-item {
padding: var(--spacing-sm);
border-bottom: 1px solid var(--border-primary);
}
.speaker {
font-weight: 600;
color: var(--primary);
}
/* Drag and Drop Styles */
.drop-zone.dragover {
background: var(--secondary);
border-color: var(--primary);
}
/* Real-time Subtitle Overlay */
.subtitle-overlay {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.8);
color: white;
padding: var(--spacing-md);
border-radius: var(--radius-md);
max-width: 80%;
text-align: center;
z-index: 20;
display: none;
}
.subtitle-overlay.visible {
display: block;
}
/* Screen Sharing Indicator */
.screen-sharing-indicator {
position: absolute;
top: 10px;
right: 10px;
background: var(--danger);
color: white;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
z-index: 25;
display: none;
}
.screen-sharing-indicator.visible {
display: block;
}
/* New Components for Missing UI Elements */
.participant-selector {
margin-bottom: var(--spacing-xl);
}
.selected-participants {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-md);
}
.participant-chip {
display: flex;
align-items: center;
gap: var(--spacing-xs);
background: var(--secondary);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-xl);
font-size: var(--font-size-sm);
}
.remove-btn {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: var(--font-size-lg);
padding: 0;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.meeting-meta-info {
background: var(--bg-tertiary);
padding: var(--spacing-lg);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-xl);
}
.meta-item {
display: flex;
align-items: flex-start;
margin-bottom: var(--spacing-md);
}
.meta-label {
font-weight: 600;
color: var(--text-primary);
min-width: 60px;
}
.meta-value {
color: var(--text-secondary);
flex: 1;
}
.minutes-section {
margin-bottom: var(--spacing-xl);
}
.minutes-content {
background: var(--bg-tertiary);
padding: var(--spacing-lg);
border-radius: var(--radius-md);
line-height: 1.6;
}
.decisions-list {
list-style: none;
padding: 0;
}
.decisions-list li {
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--border-primary);
position: relative;
padding-left: var(--spacing-lg);
}
.decisions-list li::before {
content: '✓';
position: absolute;
left: 0;
color: var(--success);
font-weight: bold;
}
.todo-item {
display: flex;
align-items: center;
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--border-primary);
}
.todo-item input {
margin-right: var(--spacing-sm);
}
.action-buttons {
display: flex;
gap: var(--spacing-md);
margin-top: var(--spacing-xl);
}
.contact-info {
background: var(--bg-tertiary);
padding: var(--spacing-lg);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-xl);
}
.info-item {
display: flex;
margin-bottom: var(--spacing-md);
}
.info-label {
font-weight: 600;
color: var(--text-primary);
min-width: 60px;
}
.info-value {
color: var(--text-secondary);
flex: 1;
}
.quick-actions {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: var(--z-modal-backdrop);
}
.modal-container {
max-width: 90%;
max-height: 80%;
overflow: hidden;
}
.modal {
background: var(--bg-primary);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-xl);
animation: material-enter 0.3s ease;
}
.modal-header {
padding: var(--spacing-lg);
border-bottom: 1px solid var(--border-primary);
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-header h3 {
margin: 0;
font-size: var(--font-size-lg);
}
.modal-close {
background: none;
border: none;
font-size: var(--font-size-xl);
cursor: pointer;
color: var(--text-secondary);
}
.modal-content {
padding: var(--spacing-lg);
max-height: 400px;
overflow-y: auto;
}
.modal-footer {
padding: var(--spacing-lg);
border-top: 1px solid var(--border-primary);
display: flex;
gap: var(--spacing-md);
justify-content: flex-end;
}
.participant-item {
display: flex;
align-items: center;
padding: var(--spacing-md) 0;
border-bottom: 1px solid var(--border-primary);
}
.participant-item:last-child {
border-bottom: none;
}
.participant-item input {
margin-right: var(--spacing-md);
}
.participant-info {
flex: 1;
display: flex;
flex-direction: column;
}
.participant-info .name {
font-weight: 600;
color: var(--text-primary);
}
.participant-info .role {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.file-upload-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: var(--spacing-lg);
}
.upload-option {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-lg);
border: 2px dashed var(--border-primary);
border-radius: var(--radius-md);
background: var(--bg-tertiary);
cursor: pointer;
transition: all var(--transition-fast);
}
.upload-option:hover {
border-color: var(--primary);
background: var(--secondary);
}
.upload-icon {
font-size: var(--font-size-xxl);
}
/* Toast Styles */
.toast-container {
position: fixed;
top: 100px;
right: var(--spacing-lg);
z-index: var(--z-tooltip);
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.toast {
background: var(--bg-primary);
border-radius: var(--radius-md);
padding: var(--spacing-md) var(--spacing-lg);
box-shadow: var(--shadow-lg);
display: flex;
align-items: center;
gap: var(--spacing-sm);
animation: slideInUp 0.3s ease;
}
.toast.success {
border-left: 4px solid var(--success);
}
.toast.error {
border-left: 4px solid var(--danger);
}
.toast.warning {
border-left: 4px solid var(--warning);
}
.toast.info {
border-left: 4px solid var(--primary);
}
/* Accessibility High Contrast Mode */
.high-contrast .ar-annotation {
background: yellow;
color: black;
border: 2px solid black;
}
.high-contrast .control-btn {
border: 2px solid white;
}
/* Language Switch Indicator */
.language-indicator {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.7);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
z-index: 15;
}
</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" onclick="switchScreen('settings')">设置</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>
<!-- Settings Screen -->
<div class="screen" id="settings">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="profile-screen">
<h2 class="section-title">设置</h2>
<div class="settings-group">
<h3 class="settings-subtitle">通用设置</h3>
<div class="setting-item">
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
<span class="setting-label">自动录制会议</span>
</label>
</div>
<div class="setting-item">
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
<span class="setting-label">自动生成会议纪要</span>
</label>
</div>
<div class="setting-item">
<label class="switch">
<input type="checkbox">
<span class="slider"></span>
<span class="setting-label">开启实时字幕</span>
</label>
</div>
</div>
<div class="settings-group">
<h3 class="settings-subtitle">视频设置</h3>
<div class="setting-item">
<span class="setting-label">视频质量</span>
<select class="setting-select">
<option>自动</option>
<option>高清 (720p)</option>
<option>标清 (480p)</option>
<option>流畅 (360p)</option>
</select>
</div>
<div class="setting-item">
<span class="setting-label">默认摄像头</span>
<select class="setting-select">
<option>后置摄像头</option>
<option>前置摄像头</option>
</select>
</div>
</div>
<div class="settings-group">
<h3 class="settings-subtitle">音频设置</h3>
<div class="setting-item">
<span class="setting-label">麦克风音量</span>
<input type="range" min="0" max="100" value="80" class="setting-slider">
</div>
<div class="setting-item">
<span class="setting-label">扬声器音量</span>
<input type="range" min="0" max="100" value="70" class="setting-slider">
</div>
</div>
<div class="settings-group">
<h3 class="settings-subtitle">语言设置</h3>
<div class="setting-item">
<span class="setting-label">应用语言</span>
<select class="setting-select" onchange="changeLanguage(this.value)">
<option value="zh">中文</option>
<option value="en">English</option>
</select>
</div>
</div>
<button class="logout-btn" onclick="switchScreen('profile')">返回</button>
</div>
</div>
<!-- AI Assistant Screen -->
<div class="screen" id="ai-assistant">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="ai-screen">
<div class="ai-header">
<button class="back-btn" onclick="switchScreen('dashboard')"></button>
<h2>AI助手</h2>
<button class="voice-btn" onclick="toggleVoiceInput()">🎤</button>
</div>
<div class="chat-container">
<div class="message ai-message">
<div class="message-avatar">AI</div>
<div class="message-content">
您好我是XSynergy AI助手请问有什么可以帮您您可以询问设备故障、操作流程或任何技术问题。
</div>
</div>
</div>
<div class="input-container">
<input type="text" class="ai-input" placeholder="输入您的问题..." onkeypress="handleAIInput(event)">
<button class="send-btn" onclick="sendAIQuestion()">发送</button>
</div>
<div class="quick-questions">
<h3>常见问题</h3>
<div class="question-chips">
<button class="question-chip" onclick="askQuestion('E-101泵的常见故障代码')">E-101泵故障代码</button>
<button class="question-chip" onclick="askQuestion('安全操作规程')">安全操作规程</button>
<button class="question-chip" onclick="askQuestion('设备维护周期')">设备维护周期</button>
</div>
</div>
</div>
</div>
<!-- Whiteboard Screen -->
<div class="screen" id="whiteboard">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="whiteboard-container">
<div class="whiteboard-header">
<button class="back-btn" onclick="switchScreen('ar-session')"></button>
<h2>共享白板</h2>
<div class="whiteboard-tools">
<button class="tool-btn active" data-tool="pen">✏️</button>
<button class="tool-btn" data-tool="text">T</button>
<button class="tool-btn" data-tool="shape"></button>
<button class="tool-btn" data-tool="eraser">🧽</button>
<button class="tool-btn" data-tool="clear">🗑️</button>
</div>
</div>
<canvas id="whiteboard-canvas" width="800" height="600"></canvas>
<div class="color-palette">
<div class="color-option selected" style="background-color: #000000;"></div>
<div class="color-option" style="background-color: #FF4D4F;"></div>
<div class="color-option" style="background-color: #0A7CFF;"></div>
<div class="color-option" style="background-color: #52C41A;"></div>
<div class="color-option" style="background-color: #FFD700;"></div>
</div>
<div class="brush-size">
<span>画笔粗细</span>
<input type="range" min="1" max="10" value="3" class="size-slider">
</div>
</div>
</div>
<!-- File Sharing Interface -->
<div class="screen" id="file-share">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="file-screen">
<div class="file-header">
<button class="back-btn" onclick="switchScreen('ar-session')"></button>
<h2>文件共享</h2>
</div>
<div class="file-upload-area" id="drop-zone">
<div class="upload-icon">📁</div>
<p>拖放文件到这里或点击选择</p>
<input type="file" id="file-input" multiple style="display: none;">
<button class="upload-btn" onclick="document.getElementById('file-input').click()">选择文件</button>
</div>
<div class="file-list">
<h3>已上传文件</h3>
<div class="file-item">
<div class="file-icon">📄</div>
<div class="file-info">
<div class="file-name">设备操作手册.pdf</div>
<div class="file-size">2.3 MB</div>
</div>
<button class="file-action">分享</button>
</div>
<div class="file-item">
<div class="file-icon">🎥</div>
<div class="file-info">
<div class="file-name">操作演示.mp4</div>
<div class="file-size">15.7 MB</div>
</div>
<button class="file-action">分享</button>
</div>
</div>
<div class="share-options">
<h3>分享选项</h3>
<div class="share-option">
<input type="checkbox" id="share-all" checked>
<label for="share-all">分享给所有参会者</label>
</div>
<div class="share-option">
<label>选择特定参会者:</label>
<select multiple class="participant-select">
<option>王工</option>
<option>张总</option>
<option>李技术员</option>
</select>
</div>
</div>
</div>
</div>
<!-- Meeting Playback Screen -->
<div class="screen" id="playback">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="playback-screen">
<div class="playback-header">
<button class="back-btn" onclick="switchScreen('history')"></button>
<h2>会议回放 - 设备检修指导</h2>
<div class="playback-info">
<span>2024-01-15 14:30</span>
<span>时长: 45:12</span>
</div>
</div>
<div class="video-player">
<video controls class="playback-video">
<source src="#" type="video/mp4">
</video>
<div class="playback-controls">
<button class="control-btn" onclick="togglePlay()">▶️</button>
<input type="range" class="progress-bar" min="0" max="100" value="0">
<span class="time-display">00:00 / 45:12</span>
<button class="control-btn" onclick="toggleFullscreen()"></button>
</div>
</div>
<div class="playback-sidebar">
<div class="sidebar-tabs">
<button class="tab-btn active" data-tab="annotations">标注</button>
<button class="tab-btn" data-tab="transcript">字幕</button>
<button class="tab-btn" data-tab="participants">参会者</button>
</div>
<div class="tab-content" id="annotations-tab">
<div class="annotation-item">
<span class="annotation-time">05:23</span>
<span class="annotation-text">检查泵体连接处</span>
</div>
<div class="annotation-item">
<span class="annotation-time">12:45</span>
<span class="annotation-text">注意压力表读数</span>
</div>
</div>
<div class="tab-content" id="transcript-tab" style="display: none;">
<div class="transcript-item">
<span class="speaker">王工:</span>
<span class="transcript-text">请检查泵体左侧的连接处是否有泄漏</span>
</div>
</div>
</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>
<!-- Meeting Schedule Screen -->
<div class="screen" id="schedule-meeting">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="profile-screen">
<div class="ai-header">
<button class="back-btn" onclick="switchScreen('dashboard')"></button>
<h2>预约会议</h2>
</div>
<div class="material-textfield">
<input type="text" class="input-field" placeholder=" " id="meeting-title" aria-label="会议主题" required>
<label for="meeting-title">会议主题</label>
</div>
<div class="material-textfield">
<input type="datetime-local" class="input-field" placeholder=" " id="meeting-time" aria-label="会议时间" required>
<label for="meeting-time">会议时间</label>
</div>
<div class="material-textfield">
<input type="number" class="input-field" placeholder=" " id="meeting-duration" aria-label="会议时长(分钟)" min="15" max="240" required>
<label for="meeting-duration">会议时长(分钟)</label>
</div>
<div class="participant-selector">
<h3 class="settings-subtitle">参会人员</h3>
<div class="selected-participants">
<div class="participant-chip">
<span class="avatar"></span>
<span>王工</span>
<button class="remove-btn">×</button>
</div>
</div>
<button class="secondary-btn" onclick="openParticipantPicker()">选择参会人</button>
</div>
<div class="material-textfield">
<textarea class="input-field" placeholder=" " id="meeting-description" aria-label="会议描述" rows="3"></textarea>
<label for="meeting-description">会议描述(可选)</label>
</div>
<button class="primary-btn" onclick="createScheduledMeeting()">创建会议</button>
</div>
</div>
<!-- Join Meeting Screen -->
<div class="screen" id="join-meeting">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="login-screen">
<div class="logo" role="heading" aria-level="1">加入会议</div>
<div class="material-textfield">
<input type="text" class="input-field" placeholder=" " id="meeting-code" aria-label="会议码" required pattern="[0-9]{6}" inputmode="numeric">
<label for="meeting-code">6位会议码</label>
</div>
<div class="material-textfield">
<input type="text" class="input-field" placeholder=" " id="display-name" aria-label="显示名称" required>
<label for="display-name">您的名称</label>
</div>
<button class="primary-btn" onclick="joinWithCode()">加入会议</button>
<div class="links">
<a href="#" class="link" onclick="switchScreen('dashboard')">返回主页</a>
</div>
</div>
</div>
<!-- Meeting Details Screen -->
<div class="screen" id="meeting-details">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="profile-screen">
<div class="ai-header">
<button class="back-btn" onclick="switchScreen('history')"></button>
<h2>会议纪要 - 设备检修指导</h2>
</div>
<div class="meeting-meta-info">
<div class="meta-item">
<span class="meta-label">时间:</span>
<span class="meta-value">2024-01-15 14:30 - 15:15 (45分钟)</span>
</div>
<div class="meta-item">
<span class="meta-label">参会人:</span>
<div class="participants-list">
<span class="avatar"></span>
<span class="avatar"></span>
<span class="avatar"></span>
</div>
</div>
</div>
<div class="minutes-section">
<h3 class="settings-subtitle">会议摘要</h3>
<div class="minutes-content">
本次会议主要讨论了E-101泵的故障排查和维修方案。专家王工通过AR标注指导现场人员检查泵体连接处和压力表读数确定了泄漏点为左侧密封圈磨损。
</div>
</div>
<div class="minutes-section">
<h3 class="settings-subtitle">关键决策</h3>
<ul class="decisions-list">
<li>立即更换左侧密封圈</li>
<li>安排本周五进行压力测试</li>
<li>更新设备维护记录</li>
</ul>
</div>
<div class="minutes-section">
<h3 class="settings-subtitle">待办事项</h3>
<div class="todo-item">
<input type="checkbox" id="todo1" checked>
<label for="todo1">采购新密封圈(负责人:李明)</label>
</div>
<div class="todo-item">
<input type="checkbox" id="todo2">
<label for="todo2">周五下午2点压力测试负责人王工</label>
</div>
</div>
<div class="action-buttons">
<button class="secondary-btn" onclick="switchScreen('playback')">查看回放</button>
<button class="primary-btn" onclick="shareMinutes()">分享纪要</button>
</div>
</div>
</div>
<!-- Contact Detail Screen -->
<div class="screen" id="contact-detail">
<div class="status-bar">
<span>9:41</span>
<span>100%</span>
</div>
<div class="profile-screen">
<div class="ai-header">
<button class="back-btn" onclick="switchScreen('contacts')"></button>
<h2>联系人详情</h2>
</div>
<div class="profile-header">
<div class="profile-avatar"></div>
<div class="profile-name">王强</div>
<div class="profile-role">技术部 · 技术专家</div>
</div>
<div class="contact-info">
<div class="info-item">
<span class="info-label">邮箱:</span>
<span class="info-value">wang.qiang@company.com</span>
</div>
<div class="info-item">
<span class="info-label">电话:</span>
<span class="info-value">+86 138 0013 8000</span>
</div>
<div class="info-item">
<span class="info-label">部门:</span>
<span class="info-value">技术部 - 设备维护组</span>
</div>
</div>
<div class="quick-actions">
<button class="large-btn" onclick="startInstantCall()">
📞 立即呼叫
</button>
<button class="secondary-btn" onclick="scheduleWithContact()">
📅 预约会议
</button>
<button class="text-btn" onclick="sendMessage()">
💬 发送消息
</button>
</div>
</div>
</div>
<!-- Modal Dialogs -->
<div class="modal-overlay" id="modal-overlay" style="display: none;">
<div class="modal-container">
<!-- Participant Picker Modal -->
<div class="modal" id="participant-picker" style="display: none;">
<div class="modal-header">
<h3>选择参会人员</h3>
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-content">
<input type="text" class="search-bar" placeholder="搜索姓名/部门" id="participant-search">
<div class="participant-list">
<div class="participant-item">
<input type="checkbox" id="part1">
<label for="part1">
<span class="avatar"></span>
<span class="participant-info">
<span class="name">王强</span>
<span class="role">技术专家</span>
</span>
</label>
</div>
<div class="participant-item">
<input type="checkbox" id="part2">
<label for="part2">
<span class="avatar"></span>
<span class="participant-info">
<span class="name">张伟</span>
<span class="role">工程师</span>
</span>
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="secondary-btn" onclick="closeModal()">取消</button>
<button class="primary-btn" onclick="confirmParticipants()">确认选择</button>
</div>
</div>
<!-- File Upload Modal -->
<div class="modal" id="file-upload-modal" style="display: none;">
<div class="modal-header">
<h3>分享文件</h3>
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-content">
<div class="file-upload-options">
<button class="upload-option" onclick="selectFile()">
<div class="upload-icon">📁</div>
<span>从手机选择</span>
</button>
<button class="upload-option" onclick="takePhoto()">
<div class="upload-icon">📷</div>
<span>拍照</span>
</button>
<button class="upload-option" onclick="recordVideo()">
<div class="upload-icon">🎥</div>
<span>录制视频</span>
</button>
</div>
</div>
</div>
<!-- Settings Modal -->
<div class="modal" id="settings-modal" style="display: none;">
<div class="modal-header">
<h3>会议设置</h3>
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-content">
<div class="setting-item">
<label class="switch">
<input type="checkbox" id="auto-record" checked>
<span class="slider"></span>
<span class="setting-label">自动录制会议</span>
</label>
</div>
<div class="setting-item">
<label class="switch">
<input type="checkbox" id="auto-subtitles">
<span class="slider"></span>
<span class="setting-label">开启实时字幕</span>
</label>
</div>
<div class="setting-item">
<span class="setting-label">视频质量</span>
<select class="setting-select">
<option>自动</option>
<option>高清</option>
<option>标清</option>
</select>
</div>
</div>
<div class="modal-footer">
<button class="primary-btn" onclick="saveSettings()">保存设置</button>
</div>
</div>
</div>
</div>
<!-- Toast Notifications -->
<div class="toast-container" id="toast-container">
<div class="toast success" style="display: none;">
<span class="toast-icon"></span>
<span class="toast-message">操作成功</span>
</div>
<div class="toast error" style="display: none;">
<span class="toast-icon"></span>
<span class="toast-message">操作失败,请重试</span>
</div>
<div class="toast warning" style="display: none;">
<span class="toast-icon">⚠️</span>
<span class="toast-message">网络连接不稳定</span>
</div>
<div class="toast info" style="display: none;">
<span class="toast-icon"></span>
<span class="toast-message">文件上传中...</span>
</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);
}
});
// Enhanced UE Interactions
// Double-tap to clear annotations (PRD requirement)
let lastTap = 0;
document.addEventListener('touchend', function(e) {
const currentTime = new Date().getTime();
const tapLength = currentTime - lastTap;
if (tapLength < 300 && tapLength > 0) {
// Double tap detected
if (AppState.currentScreen === 'ar-session') {
clearLastAnnotation();
e.preventDefault();
}
}
lastTap = currentTime;
});
function clearLastAnnotation() {
if (AppState.annotations.length > 0) {
AppState.annotations.pop();
updateARAnnotations();
announceToScreenReader('已清除最后一个标注');
}
}
function updateARAnnotations() {
// Implementation to update AR annotations display
console.log('Annotations updated:', AppState.annotations);
}
// Screen Sharing Toggle
function toggleScreenSharing() {
AppState.isScreenSharing = !AppState.isScreenSharing;
const indicator = document.querySelector('.screen-sharing-indicator');
if (indicator) {
indicator.classList.toggle('visible', AppState.isScreenSharing);
}
if (AppState.isScreenSharing) {
startScreenSharing();
announceToScreenReader('屏幕共享已开启');
} else {
stopScreenSharing();
announceToScreenReader('屏幕共享已关闭');
}
}
function startScreenSharing() {
// Implementation for screen sharing
console.log('Screen sharing started');
}
function stopScreenSharing() {
// Implementation to stop screen sharing
console.log('Screen sharing stopped');
}
// File Drag and Drop
function setupFileDragAndDrop() {
const dropZone = document.getElementById('drop-zone');
if (!dropZone) return;
dropZone.addEventListener('dragover', function(e) {
e.preventDefault();
this.classList.add('dragover');
});
dropZone.addEventListener('dragleave', function() {
this.classList.remove('dragover');
});
dropZone.addEventListener('drop', function(e) {
e.preventDefault();
this.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFileUpload(files);
}
});
const fileInput = document.getElementById('file-input');
if (fileInput) {
fileInput.addEventListener('change', function() {
handleFileUpload(this.files);
});
}
}
function handleFileUpload(files) {
for (let i = 0; i < files.length; i++) {
const file = files[i];
console.log('File uploaded:', file.name, file.size);
// Add to file list UI
addFileToUI(file);
// Upload to server (simulated)
uploadFile(file);
}
}
function addFileToUI(file) {
// Implementation to add file to UI list
console.log('Adding file to UI:', file.name);
}
function uploadFile(file) {
// Simulated file upload
console.log('Uploading file:', file.name);
}
// AI Assistant Voice Activation
let voiceRecognition = null;
function toggleVoiceInput() {
if (voiceRecognition && voiceRecognition.isListening) {
stopVoiceRecognition();
} else {
startVoiceRecognition();
}
}
function startVoiceRecognition() {
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
voiceRecognition = new SpeechRecognition();
voiceRecognition.continuous = false;
voiceRecognition.interimResults = false;
voiceRecognition.lang = 'zh-CN';
voiceRecognition.onstart = function() {
console.log('Voice recognition started');
announceToScreenReader('语音输入已开启');
};
voiceRecognition.onresult = function(event) {
const transcript = event.results[0][0].transcript;
document.querySelector('.ai-input').value = transcript;
sendAIQuestion();
};
voiceRecognition.onerror = function(event) {
console.error('Speech recognition error:', event.error);
announceToScreenReader('语音识别失败');
};
voiceRecognition.onend = function() {
console.log('Voice recognition ended');
};
voiceRecognition.start();
} else {
console.warn('Speech recognition not supported');
announceToScreenReader('当前浏览器不支持语音输入');
}
}
function stopVoiceRecognition() {
if (voiceRecognition) {
voiceRecognition.stop();
announceToScreenReader('语音输入已关闭');
}
}
// AI Assistant Functions
function handleAIInput(event) {
if (event.key === 'Enter') {
sendAIQuestion();
}
}
function sendAIQuestion() {
const input = document.querySelector('.ai-input');
const question = input.value.trim();
if (question) {
addUserMessage(question);
input.value = '';
// Simulate AI response
setTimeout(() => {
getAIResponse(question);
}, 1000);
}
}
function askQuestion(question) {
document.querySelector('.ai-input').value = question;
sendAIQuestion();
}
function addUserMessage(message) {
const chatContainer = document.querySelector('.chat-container');
const messageDiv = document.createElement('div');
messageDiv.className = 'message user-message';
messageDiv.innerHTML = `
<div class="message-avatar">您</div>
<div class="message-content">${message}</div>
`;
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function getAIResponse(question) {
// Simulated AI response
const responses = {
'E-101泵的常见故障代码': 'E-101泵常见故障代码E01 - 压力过低E02 - 温度过高E03 - 流量异常。建议检查进出口阀门和冷却系统。',
'安全操作规程': '安全操作规程1. 佩戴防护装备 2. 设备断电操作 3. 两人以上协同作业 4. 紧急停止按钮测试 5. 定期安全检查',
'设备维护周期': '设备维护周期日常检查每班次周维护每周一月保养每月第一个工作日年检每年12月'
};
const response = responses[question] || '根据您的提问我找到了相关文档建议请参考设备手册第3章第2节或联系技术支持部门获取详细指导。';
addAIMessage(response);
}
function addAIMessage(message) {
const chatContainer = document.querySelector('.chat-container');
const messageDiv = document.createElement('div');
messageDiv.className = 'message ai-message';
messageDiv.innerHTML = `
<div class="message-avatar">AI</div>
<div class="message-content">${message}</div>
`;
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
// Read the response for accessibility
announceToScreenReader('AI助手回复' + message);
}
// Annotation Persistence
function saveAnnotationSession() {
const sessionData = {
annotations: AppState.annotations,
timestamp: new Date().toISOString(),
roomId: AppState.currentRoomId
};
localStorage.setItem('annotationSession', JSON.stringify(sessionData));
console.log('Annotation session saved');
}
function loadAnnotationSession() {
const sessionData = localStorage.getItem('annotationSession');
if (sessionData) {
const data = JSON.parse(sessionData);
AppState.annotations = data.annotations || [];
updateARAnnotations();
console.log('Annotation session loaded');
}
}
// Network Quality Indicators
function updateNetworkQualityIndicator(quality) {
const indicator = document.querySelector('.network-status');
if (!indicator) return;
indicator.className = 'network-status';
switch(quality) {
case 'excellent':
indicator.classList.add('good');
indicator.title = '网络质量:优秀';
break;
case 'good':
indicator.classList.add('good');
indicator.title = '网络质量:良好';
break;
case 'fair':
indicator.classList.add('weak');
indicator.title = '网络质量:一般';
break;
case 'poor':
indicator.classList.add('offline');
indicator.title = '网络质量:较差';
break;
default:
indicator.classList.add('offline');
indicator.title = '网络连接已断开';
}
}
// Accessibility Features
function toggleHighContrastMode() {
document.body.classList.toggle('high-contrast');
const isEnabled = document.body.classList.contains('high-contrast');
localStorage.setItem('highContrastMode', isEnabled);
announceToScreenReader(isEnabled ? '高对比度模式已开启' : '高对比度模式已关闭');
}
function increaseFontSize() {
const html = document.documentElement;
const currentSize = parseFloat(getComputedStyle(html).fontSize);
html.style.fontSize = (currentSize + 2) + 'px';
localStorage.setItem('fontSize', html.style.fontSize);
}
function decreaseFontSize() {
const html = document.documentElement;
const currentSize = parseFloat(getComputedStyle(html).fontSize);
html.style.fontSize = Math.max(12, currentSize - 2) + 'px';
localStorage.setItem('fontSize', html.style.fontSize);
}
// Multi-language Support
function changeLanguage(lang) {
AppState.currentLanguage = lang;
localStorage.setItem('preferredLanguage', lang);
// Update UI language (simulated)
updateUILanguage(lang);
announceToScreenReader(lang === 'zh' ? '已切换到中文' : 'Switched to English');
}
function updateUILanguage(lang) {
// This would be implemented with i18n in a real application
console.log('Language changed to:', lang);
}
// Real-time Subtitle Display
function showSubtitle(text, speaker) {
const subtitleOverlay = document.querySelector('.subtitle-overlay');
if (subtitleOverlay) {
subtitleOverlay.textContent = speaker ? `${speaker}: ${text}` : text;
subtitleOverlay.classList.add('visible');
// Auto hide after 5 seconds
setTimeout(() => {
subtitleOverlay.classList.remove('visible');
}, 5000);
}
}
// Whiteboard Drawing
function setupWhiteboard() {
const canvas = document.getElementById('whiteboard-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('touchstart', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('touchmove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('touchend', stopDrawing);
function startDrawing(e) {
isDrawing = true;
[lastX, lastY] = getCoordinates(e);
}
function draw(e) {
if (!isDrawing) return;
e.preventDefault();
const [x, y] = getCoordinates(e);
ctx.beginPath();
ctx.lineWidth = document.querySelector('.size-slider').value;
ctx.lineCap = 'round';
ctx.strokeStyle = getSelectedColor();
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.stroke();
[lastX, lastY] = [x, y];
}
function stopDrawing() {
isDrawing = false;
}
function getCoordinates(e) {
const rect = canvas.getBoundingClientRect();
let x, y;
if (e.type.includes('touch')) {
x = e.touches[0].clientX - rect.left;
y = e.touches[0].clientY - rect.top;
} else {
x = e.clientX - rect.left;
y = e.clientY - rect.top;
}
return [x, y];
}
function getSelectedColor() {
const selected = document.querySelector('.color-option.selected');
return selected ? selected.style.backgroundColor : '#000000';
}
}
// Initialize enhanced features
function initializeEnhancedFeatures() {
setupFileDragAndDrop();
setupWhiteboard();
// Load saved preferences
const highContrast = localStorage.getItem('highContrastMode') === 'true';
if (highContrast) {
document.body.classList.add('high-contrast');
}
const fontSize = localStorage.getItem('fontSize');
if (fontSize) {
document.documentElement.style.fontSize = fontSize;
}
const language = localStorage.getItem('preferredLanguage') || 'zh';
changeLanguage(language);
}
// Add to existing initialization
window.addEventListener('DOMContentLoaded', function() {
// Existing initialization code...
initializeEnhancedFeatures();
});
// 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 = [];
// New functions for missing UI elements
function openParticipantPicker() {
document.getElementById('modal-overlay').style.display = 'flex';
document.getElementById('participant-picker').style.display = 'block';
}
function confirmParticipants() {
closeModal();
showToast('参会人员已选择', 'success');
}
function createScheduledMeeting() {
const title = document.getElementById('meeting-title').value;
if (!title) {
showToast('请输入会议主题', 'error');
return;
}
showToast('会议预约成功', 'success');
setTimeout(() => switchScreen('dashboard'), 1500);
}
function joinWithCode() {
const code = document.getElementById('meeting-code').value;
const name = document.getElementById('display-name').value;
if (!code || !name) {
showToast('请输入会议码和名称', 'error');
return;
}
if (!/^\d{6}$/.test(code)) {
showToast('请输入6位数字会议码', 'error');
return;
}
showToast('正在加入会议...', 'info');
setTimeout(() => switchScreen('meeting'), 2000);
}
function shareMinutes() {
showToast('会议纪要分享链接已复制', 'success');
}
function startInstantCall() {
showToast('正在呼叫...', 'info');
setTimeout(() => switchScreen('meeting'), 1000);
}
function scheduleWithContact() {
switchScreen('schedule-meeting');
}
function sendMessage() {
showToast('消息功能开发中', 'info');
}
function selectFile() {
showToast('文件选择功能开发中', 'info');
closeModal();
}
function takePhoto() {
showToast('拍照功能开发中', 'info');
closeModal();
}
function recordVideo() {
showToast('视频录制功能开发中', 'info');
closeModal();
}
function saveSettings() {
showToast('设置已保存', 'success');
closeModal();
}
function closeModal() {
document.getElementById('modal-overlay').style.display = 'none';
document.querySelectorAll('.modal').forEach(modal => {
modal.style.display = 'none';
});
}
function showToast(message, type = 'info') {
const toast = document.querySelector(`.toast.${type}`);
const messageEl = toast.querySelector('.toast-message');
messageEl.textContent = message;
toast.style.display = 'flex';
setTimeout(() => {
toast.style.display = 'none';
}, 3000);
}
// Add keyboard event listeners for modals
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && document.getElementById('modal-overlay').style.display === 'flex') {
closeModal();
}
});
// Close modal when clicking outside
document.getElementById('modal-overlay').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// Export new functions
window.openParticipantPicker = openParticipantPicker;
window.confirmParticipants = confirmParticipants;
window.createScheduledMeeting = createScheduledMeeting;
window.joinWithCode = joinWithCode;
window.shareMinutes = shareMinutes;
window.startInstantCall = startInstantCall;
window.scheduleWithContact = scheduleWithContact;
window.sendMessage = sendMessage;
window.selectFile = selectFile;
window.takePhoto = takePhoto;
window.recordVideo = recordVideo;
window.saveSettings = saveSettings;
window.closeModal = closeModal;
window.showToast = showToast;
</script>
</body>