dvc890 commited on
Commit
714367c
·
verified ·
1 Parent(s): e54e8e9

Upload 67 files

Browse files
components/Sidebar.tsx CHANGED
@@ -1,6 +1,5 @@
1
-
2
  import React, { useState, useEffect, useMemo } from 'react';
3
- import { LayoutDashboard, Users, BookOpen, GraduationCap, Settings, LogOut, FileText, School, UserCog, Palette, X, Building, Gamepad2, CalendarCheck, UserCircle, MessageSquare, Bot, ArrowUp, ArrowDown, Save, UserCheck, Download } from 'lucide-react';
4
  import { UserRole } from '../types';
5
  import { api } from '../services/api';
6
 
@@ -48,27 +47,57 @@ export const Sidebar: React.FC<SidebarProps> = ({ currentView, onChangeView, use
48
  const [menuItems, setMenuItems] = useState<MenuItem[]>(STATIC_MENU_ITEMS);
49
  const [isEditing, setIsEditing] = useState(false);
50
  const [installPrompt, setInstallPrompt] = useState<any>(null);
 
51
 
52
  // Capture the PWA install prompt event
53
  useEffect(() => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  const handler = (e: any) => {
55
- // Prevent the mini-infobar from appearing on mobile
56
  e.preventDefault();
57
- // Stash the event so it can be triggered later.
 
58
  setInstallPrompt(e);
 
59
  };
 
60
  window.addEventListener('beforeinstallprompt', handler);
61
  return () => window.removeEventListener('beforeinstallprompt', handler);
62
  }, []);
63
 
64
  const handleInstallClick = async () => {
65
- if (!installPrompt) return;
 
 
 
 
 
 
 
66
  // Show the install prompt
67
- installPrompt.prompt();
 
68
  // Wait for the user to respond to the prompt
69
- const { outcome } = await installPrompt.userChoice;
 
70
  if (outcome === 'accepted') {
 
71
  setInstallPrompt(null);
 
 
 
72
  }
73
  };
74
 
@@ -184,13 +213,25 @@ export const Sidebar: React.FC<SidebarProps> = ({ currentView, onChangeView, use
184
  </div>
185
 
186
  <div className="p-4 border-t border-slate-700 space-y-2 shrink-0">
187
- {installPrompt && (
188
  <button onClick={handleInstallClick} className="w-full flex items-center space-x-3 px-4 py-3 rounded-lg bg-gradient-to-r from-blue-600 to-indigo-600 text-white hover:shadow-lg transition-all duration-200 animate-in fade-in slide-in-from-bottom-2">
189
  <Download size={20} />
190
- <span className="font-bold">安装应用到桌面</span>
191
  </button>
192
  )}
193
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  <div className="px-4 py-2 text-xs text-slate-500 flex justify-between">
195
  <span>当前角色:</span>
196
  <span className="text-slate-300 font-bold">
@@ -207,4 +248,4 @@ export const Sidebar: React.FC<SidebarProps> = ({ currentView, onChangeView, use
207
  </div>
208
  </>
209
  );
210
- };
 
 
1
  import React, { useState, useEffect, useMemo } from 'react';
2
+ import { LayoutDashboard, Users, BookOpen, GraduationCap, Settings, LogOut, FileText, School, UserCog, Palette, X, Building, Gamepad2, CalendarCheck, UserCircle, MessageSquare, Bot, ArrowUp, ArrowDown, Save, UserCheck, Download, Smartphone } from 'lucide-react';
3
  import { UserRole } from '../types';
4
  import { api } from '../services/api';
5
 
 
47
  const [menuItems, setMenuItems] = useState<MenuItem[]>(STATIC_MENU_ITEMS);
48
  const [isEditing, setIsEditing] = useState(false);
49
  const [installPrompt, setInstallPrompt] = useState<any>(null);
50
+ const [isStandalone, setIsStandalone] = useState(false);
51
 
52
  // Capture the PWA install prompt event
53
  useEffect(() => {
54
+ // 1. Check if already running in standalone mode (installed)
55
+ const isStandaloneMode = window.matchMedia('(display-mode: standalone)').matches ||
56
+ (window.navigator as any).standalone === true;
57
+ setIsStandalone(isStandaloneMode);
58
+
59
+ if (isStandaloneMode) return;
60
+
61
+ // 2. Check if the event was already captured globally (in index.html)
62
+ if ((window as any).deferredPrompt) {
63
+ console.log('⚡ Using globally captured PWA prompt');
64
+ setInstallPrompt((window as any).deferredPrompt);
65
+ }
66
+
67
+ // 3. Setup listener for future events (if not yet captured)
68
  const handler = (e: any) => {
 
69
  e.preventDefault();
70
+ // Update global and local state
71
+ (window as any).deferredPrompt = e;
72
  setInstallPrompt(e);
73
+ console.log('⚡ PWA prompt event fired in component');
74
  };
75
+
76
  window.addEventListener('beforeinstallprompt', handler);
77
  return () => window.removeEventListener('beforeinstallprompt', handler);
78
  }, []);
79
 
80
  const handleInstallClick = async () => {
81
+ // Prefer local state, fallback to global
82
+ const promptEvent = installPrompt || (window as any).deferredPrompt;
83
+
84
+ if (!promptEvent) {
85
+ alert('安装功能当前不可用。请尝试点击浏览器菜单中的“添加到主屏幕”或“安装应用”。');
86
+ return;
87
+ }
88
+
89
  // Show the install prompt
90
+ promptEvent.prompt();
91
+
92
  // Wait for the user to respond to the prompt
93
+ const { outcome } = await promptEvent.userChoice;
94
+
95
  if (outcome === 'accepted') {
96
+ console.log('User accepted the install prompt');
97
  setInstallPrompt(null);
98
+ (window as any).deferredPrompt = null;
99
+ } else {
100
+ console.log('User dismissed the install prompt');
101
  }
102
  };
103
 
 
213
  </div>
214
 
215
  <div className="p-4 border-t border-slate-700 space-y-2 shrink-0">
216
+ {!isStandalone && installPrompt && (
217
  <button onClick={handleInstallClick} className="w-full flex items-center space-x-3 px-4 py-3 rounded-lg bg-gradient-to-r from-blue-600 to-indigo-600 text-white hover:shadow-lg transition-all duration-200 animate-in fade-in slide-in-from-bottom-2">
218
  <Download size={20} />
219
+ <span className="font-bold">安装到桌面/手机</span>
220
  </button>
221
  )}
222
 
223
+ {/* Fallback for iOS/Manual if prompt missing but not installed */}
224
+ {!isStandalone && !installPrompt && (
225
+ <div className="px-4 py-2 bg-slate-800 rounded-lg text-[10px] text-slate-400 border border-slate-700 flex gap-2 items-start">
226
+ <Smartphone size={14} className="mt-0.5 shrink-0"/>
227
+ <div>
228
+ 若未显示安装按钮:
229
+ <br/>iOS: 点击分享 → 添加到主屏幕
230
+ <br/>Android: 点击菜单 → 安装应用
231
+ </div>
232
+ </div>
233
+ )}
234
+
235
  <div className="px-4 py-2 text-xs text-slate-500 flex justify-between">
236
  <span>当前角色:</span>
237
  <span className="text-slate-300 font-bold">
 
248
  </div>
249
  </>
250
  );
251
+ };
components/ai/ChatPanel.tsx CHANGED
@@ -387,7 +387,6 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ currentUser }) => {
387
  <span>正在联网搜索相关信息...</span>
388
  </div>
389
  )}
390
-
391
  <div className={`p-3 rounded-2xl text-sm overflow-hidden shadow-sm ${msg.role === 'user' ? 'bg-blue-600 text-white rounded-tr-none' : 'bg-white border border-gray-200 text-gray-800 rounded-tl-none'}`}>
392
  {msg.images && msg.images.length > 0 && (
393
  <div className="flex gap-2 mb-2 flex-wrap">
@@ -546,4 +545,4 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ currentUser }) => {
546
  </div>
547
  </div>
548
  );
549
- };
 
387
  <span>正在联网搜索相关信息...</span>
388
  </div>
389
  )}
 
390
  <div className={`p-3 rounded-2xl text-sm overflow-hidden shadow-sm ${msg.role === 'user' ? 'bg-blue-600 text-white rounded-tr-none' : 'bg-white border border-gray-200 text-gray-800 rounded-tl-none'}`}>
391
  {msg.images && msg.images.length > 0 && (
392
  <div className="flex gap-2 mb-2 flex-wrap">
 
545
  </div>
546
  </div>
547
  );
548
+ };
components/ai/WorkAssistantPanel.tsx CHANGED
@@ -676,4 +676,4 @@ export const WorkAssistantPanel: React.FC<WorkAssistantPanelProps> = ({ currentU
676
  </div>
677
  </div>
678
  );
679
- };
 
676
  </div>
677
  </div>
678
  );
679
+ };
index.html CHANGED
@@ -1,4 +1,3 @@
1
-
2
  <!DOCTYPE html>
3
  <html lang="zh-CN">
4
  <head>
@@ -8,6 +7,18 @@
8
  <meta name="apple-mobile-web-app-capable" content="yes">
9
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
10
  <title>智慧校园管理系统</title>
 
 
 
 
 
 
 
 
 
 
 
 
11
  <style>
12
  /* Critical CSS for immediate loading state */
13
  body { margin: 0; background-color: #f9fafb; font-family: sans-serif; }
@@ -57,4 +68,4 @@
57
  </div>
58
  <script type="module" src="/index.tsx"></script>
59
  </body>
60
- </html>
 
 
1
  <!DOCTYPE html>
2
  <html lang="zh-CN">
3
  <head>
 
7
  <meta name="apple-mobile-web-app-capable" content="yes">
8
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
9
  <title>智慧校园管理系统</title>
10
+ <script>
11
+ // Global PWA Event Capture
12
+ // This runs before React, ensuring we don't miss the event.
13
+ window.deferredPrompt = null;
14
+ window.addEventListener('beforeinstallprompt', (e) => {
15
+ // Prevent the mini-infobar from appearing on mobile
16
+ e.preventDefault();
17
+ // Stash the event so it can be triggered later.
18
+ window.deferredPrompt = e;
19
+ console.log('✨ PWA Install Prompt captured globally');
20
+ });
21
+ </script>
22
  <style>
23
  /* Critical CSS for immediate loading state */
24
  body { margin: 0; background-color: #f9fafb; font-family: sans-serif; }
 
68
  </div>
69
  <script type="module" src="/index.tsx"></script>
70
  </body>
71
+ </html>
vite.config.ts CHANGED
@@ -11,6 +11,9 @@ export default defineConfig({
11
  registerType: 'autoUpdate', // 核心设置:检测到新内容自动更新 Service Worker
12
  includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'],
13
  manifest: {
 
 
 
14
  name: '智慧校园管理系统',
15
  short_name: '智慧校园',
16
  description: '一个综合性的学生管理系统仪表板,具有基于角色的访问控制、学生档案、课程管理和绩效分析功能。',
@@ -20,20 +23,22 @@ export default defineConfig({
20
  orientation: 'portrait',
21
  icons: [
22
  {
23
- src: 'https://cdn-icons-png.flaticon.com/512/3135/3135810.png', // 临时使用通用教育图标,实际项目中请替换为本地 public/pwa-192x192.png
24
  sizes: '192x192',
25
- type: 'image/png'
 
26
  },
27
  {
28
- src: 'https://cdn-icons-png.flaticon.com/512/3135/3135810.png', // 临时使用通用教育图标,实际项目中请替换为本地 public/pwa-512x512.png
29
  sizes: '512x512',
30
- type: 'image/png'
 
31
  },
32
  {
33
  src: 'https://cdn-icons-png.flaticon.com/512/3135/3135810.png',
34
  sizes: '512x512',
35
  type: 'image/png',
36
- purpose: 'any maskable'
37
  }
38
  ]
39
  },
@@ -75,4 +80,4 @@ export default defineConfig({
75
  }
76
  }
77
  }
78
- });
 
11
  registerType: 'autoUpdate', // 核心设置:检测到新内容自动更新 Service Worker
12
  includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'],
13
  manifest: {
14
+ id: '/', // 关键:定义应用的唯一标识,避免不同版本被识别为不同应用
15
+ scope: '/', // 关键:定义 PWA 的作用域
16
+ start_url: '/', // 关键:定义启动 URL
17
  name: '智慧校园管理系统',
18
  short_name: '智慧校园',
19
  description: '一个综合性的学生管理系统仪表板,具有基于角色的访问控制、学生档案、课程管理和绩效分析功能。',
 
23
  orientation: 'portrait',
24
  icons: [
25
  {
26
+ src: 'https://cdn-icons-png.flaticon.com/512/3135/3135810.png',
27
  sizes: '192x192',
28
+ type: 'image/png',
29
+ purpose: 'any' // 允许图标用于任何用途
30
  },
31
  {
32
+ src: 'https://cdn-icons-png.flaticon.com/512/3135/3135810.png',
33
  sizes: '512x512',
34
+ type: 'image/png',
35
+ purpose: 'any'
36
  },
37
  {
38
  src: 'https://cdn-icons-png.flaticon.com/512/3135/3135810.png',
39
  sizes: '512x512',
40
  type: 'image/png',
41
+ purpose: 'maskable' // 关键:适应 Android 自适应图标
42
  }
43
  ]
44
  },
 
80
  }
81
  }
82
  }
83
+ });