1
0
Просмотр исходного кода

feat: Establish core application layout and implement interactive metric dashboard with detailed metric visualization and tab navigation.

Insouciant21 6 месяцев назад
Родитель
Сommit
103cc89c0b

+ 4 - 4
app/analysis/AnalysisDashboard.tsx

@@ -11,10 +11,10 @@ export default function AnalysisDashboard() {
   const getHeatmapClass = (scoreStr: string) => {
     const score = parseInt(scoreStr);
     if (isNaN(score)) return "bg-surface-variant text-on-surface-variant border-outline-variant";
-    if (score >= 90) return "bg-metric-high text-on-primary border-metric-high/30 shadow-[0_0_15px_rgba(var(--color-metric-high),0.3)]";
-    if (score >= 80) return "bg-metric-mid text-on-primary border-metric-mid/30";
-    if (score >= 70) return "bg-metric-warn text-on-primary border-metric-warn/30";
-    return "bg-metric-low text-on-primary border-metric-low/30";
+    if (score >= 90) return "bg-metric-high dark:bg-[#004d40] text-white border-metric-high/30 dark:border-white/10 shadow-[0_0_15px_rgba(var(--color-metric-high),0.3)] dark:shadow-none";
+    if (score >= 80) return "bg-metric-mid dark:bg-[#01579b] text-white border-metric-mid/30 dark:border-white/10";
+    if (score >= 70) return "bg-metric-warn dark:bg-[#d84315] text-white border-metric-warn/30 dark:border-white/10";
+    return "bg-metric-low dark:bg-[#b71c1c] text-white border-metric-low/30 dark:border-white/10";
   };
 
   // Pre-calculate dimensional averages for the top macro view

+ 2 - 1
app/layout.tsx

@@ -1,6 +1,7 @@
 import type { Metadata } from "next";
 import "./globals.css"; // Global styles
 import { ThemeProvider } from "@/components/ThemeProvider";
+import { LayoutProvider } from "@/components/LayoutProvider";
 
 export const metadata: Metadata = {
   title: "SEEC-Metrics 学生评价平台",
@@ -24,7 +25,7 @@ export default function RootLayout({
           enableSystem
           disableTransitionOnChange
         >
-          {children}
+          <LayoutProvider>{children}</LayoutProvider>
         </ThemeProvider>
       </body>
     </html>

+ 2 - 2
components/DashboardContent.tsx

@@ -192,8 +192,8 @@ export default function DashboardContent() {
                       {metric.subMetrics[0]?.values[0]?.name || "综合能力评估"}
                     </td>
                     <td className={`p-4 font-mono text-sm text-${metric.colorClass}`}>{metric.avgScore}</td>
-                    <td className="p-4">
-                      <span className={`text-xs font-bold px-3 py-1 rounded-full border ${status.class}`}>
+                    <td className="p-4 whitespace-nowrap">
+                      <span className={`text-xs font-bold px-3 py-1 rounded-full border whitespace-nowrap ${status.class}`}>
                         {status.label}
                       </span>
                     </td>

+ 58 - 3
components/Header.tsx

@@ -1,14 +1,38 @@
-import { Menu, Search, Bell, HelpCircle } from "lucide-react";
+"use client";
+
+import { Menu, Search, Bell, HelpCircle, X } from "lucide-react";
 import { ThemeToggle } from "@/components/ThemeToggle";
+import { useLayout } from "@/components/LayoutProvider";
+import { useState } from "react";
+import Image from "next/image";
 
 export default function Header() {
+  const { setMobileMenuOpen } = useLayout();
+  const [isSearchOpen, setIsSearchOpen] = useState(false);
   return (
     <header className="h-20 flex items-center justify-between px-6 lg:px-8 border-b border-outline-variant/30 z-10 bg-background/95 backdrop-blur-sm sticky top-0">
       <div className="flex items-center w-full max-w-xl">
-        <button className="lg:hidden mr-4 text-secondary hover:text-on-surface transition-colors">
+        <button 
+          className="lg:hidden mr-4 shrink-0 text-secondary hover:text-on-surface transition-colors"
+          onClick={() => setMobileMenuOpen(true)}
+        >
           <Menu className="w-6 h-6" />
         </button>
-        <div className="relative w-full">
+
+        {/* Mobile Banner */}
+        <div className="lg:hidden flex-1 flex items-center">
+          <Image 
+            src="/banner.png" 
+            alt="SEEC Metrics Banner" 
+            width={120} 
+            height={30} 
+            className="object-contain"
+            priority
+          />
+        </div>
+        
+        {/* Desktop Search (hidden on mobile) */}
+        <div className="relative w-full hidden lg:block">
           <span className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
             <Search className="w-5 h-5 text-outline" />
           </span>
@@ -18,7 +42,38 @@ export default function Header() {
             type="text"
           />
         </div>
+        
+        {/* Mobile Search Button */}
+        <button 
+          className="lg:hidden p-2 text-secondary hover:text-on-surface"
+          onClick={() => setIsSearchOpen(true)}
+        >
+          <Search className="w-5 h-5" />
+        </button>
       </div>
+
+      {/* Mobile Expanded Search Overlay */}
+      {isSearchOpen && (
+        <div className="absolute inset-y-0 left-0 right-0 bg-background/95 backdrop-blur-sm z-20 flex items-center px-4 lg:hidden">
+          <div className="relative w-full flex items-center">
+            <span className="absolute left-3 flex items-center pointer-events-none">
+              <Search className="w-5 h-5 text-outline" />
+            </span>
+            <input
+              autoFocus
+              className="w-full py-2 pl-10 pr-10 bg-surface-container-high border-none rounded-full focus:ring-1 focus:ring-primary text-sm placeholder-outline text-on-surface outline-none"
+              placeholder="搜索能力指标、学生名单..."
+              type="text"
+            />
+            <button 
+              className="absolute right-3 p-1 rounded-full hover:bg-surface-variant text-secondary"
+              onClick={() => setIsSearchOpen(false)}
+            >
+              <X className="w-4 h-4" />
+            </button>
+          </div>
+        </div>
+      )}
       <div className="flex items-center space-x-3">
         <ThemeToggle />
         <button className="p-2 rounded-full hover:bg-surface-container-high text-secondary hover:text-on-surface transition-colors relative">

+ 28 - 0
components/LayoutProvider.tsx

@@ -0,0 +1,28 @@
+"use client";
+
+import React, { createContext, useContext, useState } from "react";
+
+interface LayoutContextType {
+  mobileMenuOpen: boolean;
+  setMobileMenuOpen: (open: boolean) => void;
+}
+
+const LayoutContext = createContext<LayoutContextType | undefined>(undefined);
+
+export function LayoutProvider({ children }: { children: React.ReactNode }) {
+  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
+
+  return (
+    <LayoutContext.Provider value={{ mobileMenuOpen, setMobileMenuOpen }}>
+      {children}
+    </LayoutContext.Provider>
+  );
+}
+
+export function useLayout() {
+  const context = useContext(LayoutContext);
+  if (context === undefined) {
+    throw new Error("useLayout must be used within a LayoutProvider");
+  }
+  return context;
+}

+ 27 - 8
components/Sidebar.tsx

@@ -10,11 +10,26 @@ import {
 import Image from "next/image";
 import Link from "next/link";
 import { usePathname } from "next/navigation";
+import { useLayout } from "@/components/LayoutProvider";
 
 export default function Sidebar() {
   const pathname = usePathname();
+  const { mobileMenuOpen, setMobileMenuOpen } = useLayout();
   return (
-    <aside className="w-20 lg:w-72 bg-surface border-r border-outline-variant flex flex-col justify-between z-20 shrink-0">
+    <>
+      {/* Mobile backdrop */}
+      {mobileMenuOpen && (
+        <div 
+          className="fixed inset-0 bg-background/80 backdrop-blur-sm z-30 lg:hidden"
+          onClick={() => setMobileMenuOpen(false)}
+        />
+      )}
+      
+      <aside 
+        className={`fixed inset-y-0 left-0 bg-surface border-r border-outline-variant flex flex-col justify-between z-40 w-64 lg:w-72 lg:static shrink-0 transition-transform duration-300 ease-in-out ${
+          mobileMenuOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
+        }`}
+      >
       <div>
         <div className="h-20 flex items-center justify-center px-4 lg:px-6 w-full border-b border-outline-variant/30">
           <Link href="/" className="flex items-center justify-center hover:opacity-80 transition-opacity w-full">
@@ -23,10 +38,9 @@ export default function Sidebar() {
               alt="SEEC Metrics Banner" 
               width={160} 
               height={40} 
-              className="object-contain hidden lg:block mx-auto"
+              className="object-contain mx-auto"
               priority
             />
-            <GraduationCap className="text-primary w-8 h-8 cursor-pointer lg:hidden mx-auto" />
           </Link>
         </div>
         <nav className="mt-4 px-3 space-y-2">
@@ -47,8 +61,8 @@ export default function Sidebar() {
                 className={`flex items-center px-4 py-3 rounded-full group transition-colors ${activeClass}`}
                 href={item.href}
               >
-                <item.icon className="w-5 h-5" />
-                <span className="ml-3 font-medium hidden lg:block text-sm">
+                <item.icon className="w-5 h-5 shrink-0" />
+                <span className="ml-3 font-medium text-sm">
                   {item.name}
                 </span>
               </Link>
@@ -56,8 +70,12 @@ export default function Sidebar() {
           })}
         </nav>
       </div>
-      <div className="p-4 lg:p-6">
-        <Link href="/profile" className="flex items-center p-3 rounded-xl bg-surface-container border border-outline-variant/30 hover:bg-surface-container-high cursor-pointer transition-colors group">
+      <div className="p-4 lg:p-6 mt-auto">
+        <Link 
+          href="/profile" 
+          className="flex items-center justify-start p-3 rounded-xl bg-surface-container border border-outline-variant/30 hover:bg-surface-container-high cursor-pointer transition-colors group"
+          onClick={() => setMobileMenuOpen(false)}
+        >
           <div className="relative shrink-0">
             <Image
               alt="User Profile"
@@ -69,12 +87,13 @@ export default function Sidebar() {
             />
             <span className="absolute bottom-0 right-0 h-2.5 w-2.5 bg-green-400 rounded-full ring-2 ring-surface-container group-hover:ring-surface-container-high transition-colors"></span>
           </div>
-          <div className="ml-3 hidden lg:block">
+          <div className="ml-3">
             <p className="text-sm font-medium text-on-surface group-hover:text-primary transition-colors">刘钦</p>
             <p className="text-xs text-outline group-hover:text-secondary transition-colors">指导教师</p>
           </div>
         </Link>
       </div>
     </aside>
+    </>
   );
 }