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