0235967 1 周之前
父節點
當前提交
69dd9e6ca8

+ 73 - 80
travel-web/src/lib/ncloud.ts

@@ -2,6 +2,7 @@
 
 let serverURL = `https://dev.fmode.cn/parse`;
 if (location.protocol == "http://127.0.0.1:4040/apps") {
+    //serverURL = `http://dev.fmode.cn:1337/parse`;
     serverURL = `http://dev.fmode.cn:1337/parse`;
 }
 
@@ -227,7 +228,7 @@ export class CloudQuery {
 // CloudUser.ts
 export class CloudUser extends CloudObject {
     constructor() {
-        super("_User"); // 假设用户类在Parse中是"_User"
+        super("UserTravel"); // 假设用户类在Parse中是"UserTravel"
         // 读取用户缓存信息
         let userCacheStr = localStorage.getItem("NCloud/dev/User")
         if (userCacheStr) {
@@ -242,11 +243,18 @@ export class CloudUser extends CloudObject {
     sessionToken: string | null = ""
     /** 获取当前用户信息 */
     async current() {
-        if (!this.sessionToken) {
+        if (!this.sessionToken || !this.id) {
             console.error("用户未登录");
             return null;
         }
-        return this;
+        // 可选:查询 UserTravel 表验证用户是否存在(增强安全性)
+        const query = new CloudQuery("UserTravel");
+        const user = await query.get(this.id);
+        if (user) return this;
+        else {
+            this.clearUserCache(); // 缓存无效时清除
+            return null;
+        }
         // const response = await fetch(serverURL + `/users/me`, {
         //     headers: {
         //         "x-parse-application-id": "dev",
@@ -264,60 +272,41 @@ export class CloudUser extends CloudObject {
     }
 
     /** 登录 */
-    async login(username: string, password: string): Promise<CloudUser | null> {
-        const response = await fetch(serverURL + `/login`, {
-            headers: {
-                "x-parse-application-id": "dev",
-                "Content-Type": "application/json"
-            },
-            body: JSON.stringify({ username, password }),
-            method: "POST"
-        });
-
-        const result = await response?.json();
-        if (result?.error) {
-            console.error(result?.error);
+    // CloudUser.ts
+async login(username: string, password: string) {
+    const query = new CloudQuery("UserTravel");
+    query.equalTo("username", username);
+    query.equalTo("password", password); // 注意:生产环境应加密比较
+    
+    try {
+        const user = await query.first();
+        if (!user) {
+            console.error('用户名或密码错误');
             return null;
         }
-
-        // 设置用户信息
-        this.id = result?.objectId;
-        this.sessionToken = result?.sessionToken;
-        this.data = result; // 保存用户数据
-        // 缓存用户信息
-        console.log(result)
-        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        
+        // 登录成功,生成会话令牌
+        this.id = user.id;
+        this.sessionToken = `custom_${user.id}_${Date.now()}`;
+        this.data = user.data;
+        
+        localStorage.setItem("NCloud/dev/User", JSON.stringify({
+            ...this.data,
+            objectId: this.id,
+            sessionToken: this.sessionToken
+        }));
+        
         return this;
+    } catch (error) {
+        console.error('登录查询失败:', error);
+        return null;
     }
+}
 
     /** 登出 */
     async logout() {
-        if (!this.sessionToken) {
-            console.error("用户未登录");
-            return;
-        }
-
-        const response = await fetch(serverURL + `/logout`, {
-            headers: {
-                "x-parse-application-id": "dev",
-                "x-parse-session-token": this.sessionToken
-            },
-            method: "POST"
-        });
-
-        let result = await response?.json();
-
-        if (result?.error) {
-            console.error(result?.error);
-            if (result?.error == "Invalid session token") {
-                this.clearUserCache()
-                return true;
-            }
-            return false;
-        }
-
-        this.clearUserCache()
-        return true;
+        this.clearUserCache();
+        return true; // 无需调用后端接口,直接清除本地状态
     }
     clearUserCache() {
         // 清除用户信息
@@ -328,38 +317,42 @@ export class CloudUser extends CloudObject {
     }
 
     /** 注册 */
-    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
-        const userData = {
-            username,
-            password,
-            ...additionalData // 合并额外的用户数据
-        };
-
-        const response = await fetch(serverURL + `/users`, {
-            headers: {
-                "x-parse-application-id": "dev",
-                "Content-Type": "application/json"
-            },
-            body: JSON.stringify(userData),
-            method: "POST"
-        });
-
-        const result = await response?.json();
-        if (result?.error) {
-            console.error(result?.error);
-            return null;
-        }
-
-        // 设置用户信息
-        // 缓存用户信息
-        console.log(result)
-        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
-        this.id = result?.objectId;
-        this.sessionToken = result?.sessionToken;
-        this.data = result; // 保存用户数据
-        return this;
+    // CloudUser.ts
+async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+    const userData = {
+        username,
+        password,
+        ...additionalData
+    };
+
+    // 关键修改:使用自定义表的创建接口
+    const response = await fetch(serverURL + `/classes/Usertable`, {
+        headers: {
+            "x-parse-application-id": "dev",
+            "Content-Type": "application/json"
+        },
+        body: JSON.stringify(userData),
+        method: "POST"
+    });
+
+    const result = await response?.json();
+    if (result?.error) {
+        console.error('注册失败:', result.error);
+        return null;
     }
 
+    // 保存用户信息到缓存
+    this.id = result.objectId;
+    this.sessionToken = `custom_${result.objectId}_${Date.now()}`; // 自定义会话令牌
+    this.data = result;
+    localStorage.setItem("NCloud/dev/User", JSON.stringify({
+        ...result,
+        sessionToken: this.sessionToken
+    }));
+    
+    return this;
+}
+
     override async save() {
         let method = "POST";
         let url = serverURL + `/users`;

+ 3 - 24
travel-web/src/modules/pc-home/pages/page-association/page-association.html

@@ -54,31 +54,10 @@
   
   <div class="constitution-container">
     <div class="search-box">
-      <input #searchInput type="text" class="search-input" 
-      placeholder="输入关键词检索协会章程 (如:红色文化、会员权利...)" 
-      (keyup.enter)="searchConstitution(searchInput.value)"
-      [(ngModel)]="searchTerm">
-      <button class="search-btn" (click)="searchConstitution(searchInput.value)">
-        <i class="fas fa-search"></i> 智能检索
-      </button>
-      <button class="clear-btn" (click)="clearSearch()" *ngIf="searchTerm">
-        <i class="fas fa-times"></i> 清除
-      </button>
+      <input #searchInput type="text" class="search-input" placeholder="输入关键词检索协会章程 (如:红色文化、会员权利...)" (keyup.enter)="highlightSearch(searchInput.value)">
+      <button class="search-btn" (click)="highlightSearch(searchInput.value)"><i class="fas fa-search"></i> 智能检索</button>
     </div>
-
-    <div class="search-results" *ngIf="searchTerm && highlightCount > 0">
-      <p>找到 <strong>{{highlightCount}}</strong> 处匹配 "<strong>{{searchTerm}}</strong>" 的内容</p>
-      <div class="navigation-buttons">
-        <button (click)="navigateHighlight(-1)" [disabled]="currentHighlightIndex === 0">
-          <i class="fas fa-chevron-up"></i> 上一个
-        </button>
-        <button (click)="navigateHighlight(1)" [disabled]="currentHighlightIndex === highlightCount - 1">
-          <i class="fas fa-chevron-down"></i> 下一个
-        </button>
-      </div>
-    </div>
-
-    <div class="constitution-content" #constitutionContent>
+    <div class="constitution-content">
       <h3>江西省数字文化发展协会章程</h3>
       <p>(2023年6月修订)</p>
       

+ 52 - 222
travel-web/src/modules/pc-home/pages/page-association/page-association.scss

@@ -1,16 +1,14 @@
-// 颜色变量定义
 :host {
-  --wood-primary: #8B4513;   // 木质主色
-  --wood-secondary: #A0522D; // 木质辅色
-  --wood-light: #DEB887;     // 木质亮色
-  --brick-color: #7D5A50;    // 青砖色
-  --porcelain-blue: #2a5daa; // 青花蓝
-  --gold-yellow: #e8c34d;    // 稻穗金
-  --mountain-green: #4a6b3d; // 山林绿
-  --river-blue: #4a86e8;     // 赣江蓝
+  --wood-primary: #8B4513;   /* 木质主色 */
+  --wood-secondary: #A0522D; /* 木质辅色 */
+  --wood-light: #DEB887;     /* 木质亮色 */
+  --brick-color: #7D5A50;    /* 青砖色 */
+  --porcelain-blue: #2a5daa; /* 青花蓝 */
+  --gold-yellow: #e8c34d;    /* 稻穗金 */
+  --mountain-green: #4a6b3d; /* 山林绿 */
+  --river-blue: #4a86e8;     /* 赣江蓝 */
 }
 
-// 基础样式
 * {
   margin: 0;
   padding: 0;
@@ -26,9 +24,9 @@ body {
   background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%23f5f2e9"/><path d="M0,0 L100,100 M100,0 L0,100" stroke="%23e8e0d022" stroke-width="1"/></svg>');
 }
 
-// 页面标题
+/* 页面标题 */
 .page-header {
-  background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.7)), 
+  background: linear-gradient(lch(31.77% 44.55 69.48 / 0.983)), 
               url('https://images.unsplash.com/photo-1606193431680-8f5a06d61c9b?ixlib=rb-4.0.3&auto=format&fit=crop&w=1950&q=80') center/cover;
   height: 300px;
   display: flex;
@@ -38,7 +36,6 @@ body {
   color: white;
   text-align: center;
   position: relative;
-  border-bottom: 5px solid var(--wood-primary);
 }
 
 .page-title {
@@ -46,24 +43,18 @@ body {
   font-weight: bold;
   margin-bottom: 1rem;
   text-shadow: 0 2px 10px rgba(0,0,0,0.5);
-  font-family: 'SimSun', serif;
 }
 
 .page-subtitle {
   font-size: 1.5rem;
   max-width: 800px;
-  padding: 0 2rem;
 }
 
-// 内容区域
+/* 内容区域 */
 .content-section {
   padding: 4rem 5%;
   background-color: #fcfaf5;
   position: relative;
-  margin: 2rem auto;
-  max-width: 1200px;
-  box-shadow: 0 0 20px rgba(0,0,0,0.1);
-  border-radius: 8px;
 }
 
 .content-section::before {
@@ -74,7 +65,6 @@ body {
   right: 0;
   height: 10px;
   background: linear-gradient(90deg, var(--wood-primary), var(--wood-secondary));
-  border-radius: 8px 8px 0 0;
 }
 
 .section-header {
@@ -93,7 +83,6 @@ body {
   background: linear-gradient(90deg, var(--wood-light), transparent 80%);
   padding: 0.5rem 1.5rem;
   border-radius: 0 30px 30px 0;
-  font-family: 'SimSun', serif;
 }
 
 .section-title::before {
@@ -115,7 +104,7 @@ body {
   margin-top: 2rem;
 }
 
-// 走进协会
+/* 走进协会 */
 .org-chart-container {
   flex: 1;
   min-width: 300px;
@@ -133,12 +122,6 @@ body {
   color: white;
   display: flex;
   align-items: center;
-  gap: 1rem;
-  font-size: 1.2rem;
-}
-
-.org-header i {
-  font-size: 1.5rem;
 }
 
 .org-body {
@@ -162,7 +145,6 @@ body {
   display: flex;
   border: 2px solid var(--wood-light);
   transition: all 0.3s ease;
-  position: relative;
 }
 
 .leader-card:hover {
@@ -177,10 +159,10 @@ body {
   background-position: center;
   background-repeat: no-repeat;
   position: relative;
-  border-radius: 50% / 60%;
+  border-radius: 50% / 60%; /* 水平半径50%,垂直半径60% */
   overflow: hidden;
-  margin: 1rem;
   
+  /* 移除或修改原有的伪元素样式 */
   &::after {
     content: '';
     position: absolute;
@@ -189,9 +171,10 @@ body {
     width: 100%;
     height: 100%;
     border: 3px solid var(--wood-light);
-    border-radius: 50% / 60%;
+    border-radius: 50% / 60%; /* 与父元素保持一致 */
     box-sizing: border-box;
     pointer-events: none;
+    /* 调整渐变效果 */
     background: radial-gradient(circle, transparent 60%, rgba(0,0,0,0.2));
   }
 }
@@ -216,19 +199,16 @@ body {
   font-size: 1.5rem;
   color: var(--wood-primary);
   margin-bottom: 0.5rem;
-  font-family: 'SimSun', serif;
 }
 
 .leader-title {
   color: var(--wood-secondary);
   margin-bottom: 1rem;
-  font-size: 0.95rem;
 }
 
 .leader-desc {
   color: #666;
   font-size: 0.95rem;
-  line-height: 1.7;
 }
 
 .ar-tag {
@@ -240,10 +220,9 @@ body {
   padding: 0.3rem 0.8rem;
   border-radius: 30px;
   font-size: 0.8rem;
-  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
 }
 
-// 协会章程
+/* 协会章程 */
 .constitution-container {
   margin-top: 3rem;
   background: white;
@@ -255,118 +234,51 @@ body {
 }
 
 .search-box {
-  position: relative;
-  display: flex;
-  gap: 10px;
   padding: 1.5rem;
-  background: linear-gradient(135deg, var(--porcelain-blue) 0%, var(--river-blue) 100%);
-  border-radius: 12px 12px 0 0;
-  
-  &::before {
-    content: '';
-    position: absolute;
-    top: -50px;
-    right: -50px;
-    width: 150px;
-    height: 150px;
-    background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M20,50 Q40,20 60,50 T100,50" fill="none" stroke="white" stroke-width="1" opacity="0.2"/></svg>');
-    background-size: contain;
-    transform: rotate(20deg);
-  }
+  background: linear-gradient(135deg, #4a86e8 0%, #2a5daa 100%);
+  display: flex;
+  align-items: center;
+  position: relative;
+  overflow: hidden;
+}
+
+.search-box::before {
+  content: '';
+  position: absolute;
+  top: -50px;
+  right: -50px;
+  width: 150px;
+  height: 150px;
+  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M20,50 Q40,20 60,50 T100,50" fill="none" stroke="white" stroke-width="1" opacity="0.2"/></svg>');
+  background-size: contain;
+  transform: rotate(20deg);
 }
 
 .search-input {
   flex: 1;
-  padding: 0.8rem 1.5rem;
+  padding: 1rem 1.5rem;
   border: none;
   border-radius: 50px;
-  font-size: 1rem;
-  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
-  transition: all 0.3s ease;
-  
-  &:focus {
-    outline: none;
-    box-shadow: 0 2px 12px rgba(0,0,0,0.2);
-  }
+  font-size: 1.1rem;
+  box-shadow: 0 4px 10px rgba(0,0,0,0.2);
 }
 
-.search-btn, .clear-btn {
-  padding: 0.8rem 1.5rem;
+.search-btn {
+  background: var(--gold-yellow);
+  color: var(--wood-primary);
   border: none;
+  padding: 1rem 2rem;
   border-radius: 50px;
+  margin-left: 1rem;
   font-weight: bold;
   cursor: pointer;
   transition: all 0.3s ease;
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-
-.search-btn {
-  background: var(--gold-yellow);
-  color: var(--wood-primary);
-  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
-  
-  &:hover {
-    background: #d9b347;
-    transform: translateY(-2px);
-    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
-  }
+  box-shadow: 0 4px 10px rgba(0,0,0,0.2);
 }
 
-.clear-btn {
-  background: #f44336;
-  color: white;
-  
-  &:hover {
-    background: #d32f2f;
-  }
-}
-
-.search-results {
-  padding: 1rem 1.5rem;
-  background: rgba(255,255,255,0.9);
-  border-bottom: 1px solid #eee;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  
-  p {
-    margin: 0;
-    color: var(--wood-primary);
-    font-weight: bold;
-  }
-}
-
-.navigation-buttons {
-  display: flex;
-  gap: 10px;
-  
-  button {
-    padding: 0.5rem 1rem;
-    border: 1px solid var(--wood-light);
-    background: white;
-    color: var(--wood-primary);
-    border-radius: 4px;
-    cursor: pointer;
-    transition: all 0.2s ease;
-    display: flex;
-    align-items: center;
-    
-    &:hover:not(:disabled) {
-      background: var(--wood-light);
-      color: white;
-    }
-    
-    &:disabled {
-      opacity: 0.5;
-      cursor: not-allowed;
-    }
-    
-    i {
-      margin-right: 5px;
-    }
-  }
+.search-btn:hover {
+  background: #d9b347;
+  transform: translateY(-2px);
 }
 
 .constitution-content {
@@ -377,7 +289,6 @@ body {
   font-family: 'SimSun', serif;
   line-height: 2;
   position: relative;
-  border-radius: 0 0 12px 12px;
 }
 
 .constitution-content::before {
@@ -393,32 +304,13 @@ body {
 }
 
 .highlight {
-  background-color: rgba(232, 195, 77, 0.5);
-  color: var(--wood-primary);
+  background-color: rgba(232, 195, 77, 0.3);
   padding: 0 2px;
   border-radius: 2px;
   font-weight: bold;
-  transition: all 0.2s ease;
-  
-  &.active-highlight {
-    background-color: var(--gold-yellow);
-    box-shadow: 0 0 0 2px var(--gold-yellow);
-    animation: pulse 1s infinite alternate;
-    position: relative;
-    z-index: 1;
-  }
 }
 
-@keyframes pulse {
-  from {
-    box-shadow: 0 0 0 0 rgba(232, 195, 77, 0.7);
-  }
-  to {
-    box-shadow: 0 0 0 5px rgba(232, 195, 77, 0);
-  }
-}
-
-// 学术体系
+/* 学术体系 */
 .academic-calendar {
   flex: 1;
   min-width: 300px;
@@ -435,11 +327,6 @@ body {
   color: white;
   display: flex;
   align-items: center;
-  gap: 1rem;
-}
-
-.calendar-header i {
-  font-size: 1.5rem;
 }
 
 .calendar-body {
@@ -463,11 +350,6 @@ body {
   color: white;
   display: flex;
   align-items: center;
-  gap: 1rem;
-}
-
-.standards-header i {
-  font-size: 1.5rem;
 }
 
 .standards-body {
@@ -495,7 +377,6 @@ body {
   margin-bottom: 1rem;
   padding-bottom: 0.5rem;
   border-bottom: 2px solid var(--wood-light);
-  font-family: 'SimSun', serif;
 }
 
 .version-content {
@@ -505,13 +386,11 @@ body {
 .diff-highlight {
   background-color: rgba(255, 0, 0, 0.1);
   text-decoration: line-through;
-  cursor: pointer;
 }
 
 .new-highlight {
   background-color: rgba(0, 128, 0, 0.1);
   font-weight: bold;
-  cursor: pointer;
 }
 
 .diff-highlight.active-highlight {
@@ -522,7 +401,7 @@ body {
   background-color: rgba(0, 128, 0, 0.2);
 }
 
-// 人才培养
+/* 人才培养 */
 .training-container {
   display: flex;
   flex-wrap: wrap;
@@ -546,11 +425,6 @@ body {
   color: white;
   display: flex;
   align-items: center;
-  gap: 1rem;
-}
-
-.training-header i {
-  font-size: 1.5rem;
 }
 
 .training-body {
@@ -640,7 +514,6 @@ body {
   font-size: 1.2rem;
   color: var(--wood-primary);
   margin-bottom: 0.5rem;
-  font-family: 'SimSun', serif;
 }
 
 .course-meta {
@@ -651,13 +524,12 @@ body {
   margin-top: 1rem;
 }
 
-// 页脚
+/* 页脚 */
 .footer {
   background: var(--wood-primary);
   color: #f0e6d2;
   padding: 3rem 5% 2rem;
   margin-top: 3rem;
-  border-top: 5px solid var(--wood-secondary);
 }
 
 .footer-content {
@@ -665,8 +537,6 @@ body {
   flex-wrap: wrap;
   gap: 3rem;
   margin-bottom: 2rem;
-  max-width: 1200px;
-  margin: 0 auto 2rem;
 }
 
 .footer-column {
@@ -679,7 +549,6 @@ body {
   margin-bottom: 1.5rem;
   position: relative;
   padding-bottom: 0.5rem;
-  font-family: 'SimSun', serif;
 }
 
 .footer-column h3::after {
@@ -698,9 +567,6 @@ body {
 
 .footer-column ul li {
   margin-bottom: 0.8rem;
-  display: flex;
-  align-items: center;
-  gap: 0.5rem;
 }
 
 .footer-column a {
@@ -719,11 +585,9 @@ body {
   border-top: 1px solid rgba(240, 230, 210, 0.3);
   color: #d9c7a7;
   font-size: 0.9rem;
-  max-width: 1200px;
-  margin: 0 auto;
 }
 
-// 动画效果
+/* 动画效果 */
 @keyframes float {
   0% { transform: translateY(0px); }
   50% { transform: translateY(-10px); }
@@ -734,7 +598,7 @@ body {
   animation: float 3s ease-in-out infinite;
 }
 
-// 响应式设计
+/* 响应式设计 */
 @media (max-width: 768px) {
   .section-content, .versions, .training-container {
     flex-direction: column;
@@ -747,39 +611,5 @@ body {
   .leader-img {
     width: 100%;
     height: 200px;
-    margin: 0;
-    border-radius: 0;
-  }
-  
-  .search-box {
-    flex-direction: column;
-    
-    button {
-      width: 100%;
-    }
-  }
-  
-  .search-results {
-    flex-direction: column;
-    gap: 1rem;
-    align-items: flex-start;
-  }
-}
-
-@media (max-width: 480px) {
-  .page-title {
-    font-size: 2.5rem;
-  }
-  
-  .page-subtitle {
-    font-size: 1.2rem;
-  }
-  
-  .section-title {
-    font-size: 1.8rem;
-  }
-  
-  .constitution-content {
-    padding: 1rem;
   }
 }

+ 38 - 256
travel-web/src/modules/pc-home/pages/page-association/page-association.ts

@@ -1,26 +1,17 @@
 import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
 import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
 import * as echarts from 'echarts';
 
 @Component({
   selector: 'app-page-association',
   standalone: true,
-  imports: [CommonModule, FormsModule],
+  imports: [CommonModule],
   templateUrl: './page-association.html',
   styleUrls: ['./page-association.scss']
 })
 export class PageAssociation implements AfterViewInit {
-  // 视图子元素引用
   @ViewChild('orgChart') orgChartElement!: ElementRef;
   @ViewChild('academicCalendar') academicCalendarElement!: ElementRef;
-  @ViewChild('constitutionContent') constitutionContent!: ElementRef;
-
-  // 搜索相关属性
-  searchTerm: string = '';
-  highlightCount: number = 0;
-  currentHighlightIndex: number = -1;
-  highlightElements: HTMLElement[] = [];
 
   // 课程数据
   courses = [
@@ -50,52 +41,41 @@ export class PageAssociation implements AfterViewInit {
     }
   ];
 
-  // 初始化生命周期钩子
   ngAfterViewInit(): void {
     this.initOrgChart();
     this.initAcademicCalendar();
   }
 
-  /**
-   * 初始化组织架构图
-   */
   initOrgChart(): void {
     const orgChart = echarts.init(this.orgChartElement.nativeElement);
-    const option = {
+    orgChart.setOption({
       tooltip: {
         trigger: 'item',
-        triggerOn: 'mousemove',
-        formatter: (params: any) => {
-          return `<strong>${params.name}</strong><br/>${params.data.department || '江西省数字文化发展协会'}`;
-        }
+        triggerOn: 'mousemove'
       },
       series: [
         {
           type: 'tree',
           data: [{
             name: '协会主席',
-            department: '全面负责协会工作',
             children: [{
               name: '常务副主席',
-              department: '分管日常事务',
               children: [
-                { name: '会员服务部', department: '会员发展与服务' },
-                { name: '人才培养部', department: '专业培训与认证' }
+                { name: '会员服务部' },
+                { name: '人才培养部' }
               ]
             }, {
               name: '秘书长',
-              department: '协调各部门工作',
               children: [
-                { name: '技术研发中心', department: '数字技术研发' },
-                { name: '平台建设部', department: '系统平台开发' }
+                { name: '技术研发中心' },
+                { name: '平台建设部' }
               ]
             }, {
               name: '学术委员会',
-              department: '学术研究与标准制定',
               children: [
-                { name: '红色文化研究所', department: '红色资源数字化' },
-                { name: '非遗研究中心', department: '非遗保护与传承' },
-                { name: '数字技术实验室', department: '前沿技术研究' }
+                { name: '红色文化研究所' },
+                { name: '非遗研究中心' },
+                { name: '数字技术实验室' }
               ]
             }]
           }],
@@ -104,14 +84,13 @@ export class PageAssociation implements AfterViewInit {
           bottom: '5%',
           right: '20%',
           symbolSize: 20,
-          symbol: 'roundRect',
+          symbol: 'circle',
           orient: 'vertical',
           expandAndCollapse: true,
           initialTreeDepth: 2,
           lineStyle: {
             width: 2,
-            color: '#8B4513',
-            curveness: 0.2
+            color: '#8B4513'
           },
           label: {
             position: 'top',
@@ -119,21 +98,17 @@ export class PageAssociation implements AfterViewInit {
             align: 'middle',
             fontSize: 14,
             fontWeight: 'bold',
-            color: '#8B4513',
-            fontFamily: 'Microsoft YaHei'
+            color: '#8B4513'
           },
           itemStyle: {
             color: '#DEB887',
             borderColor: '#8B4513',
-            borderWidth: 2,
-            borderRadius: 5
+            borderWidth: 2
           },
           emphasis: {
             itemStyle: {
               color: '#e8c34d',
-              borderColor: '#8B4513',
-              shadowBlur: 10,
-              shadowColor: 'rgba(0, 0, 0, 0.3)'
+              borderColor: '#8B4513'
             }
           },
           leaves: {
@@ -149,26 +124,17 @@ export class PageAssociation implements AfterViewInit {
           }
         }
       ]
-    };
+    });
     
-    orgChart.setOption(option);
     window.addEventListener('resize', () => orgChart.resize());
   }
 
-  /**
-   * 初始化学术日历
-   */
   initAcademicCalendar(): void {
     const academicCalendar = echarts.init(this.academicCalendarElement.nativeElement);
-    const option = {
+    academicCalendar.setOption({
       tooltip: {
-        formatter: (params: any) => {
-          return `
-            <div style="font-weight:bold;margin-bottom:5px">${params.data.event}</div>
-            <div>日期: ${params.name}</div>
-            <div>热度: ${params.value[1]}</div>
-            <div>地点: ${params.data.location}</div>
-          `;
+        formatter: function(params: any) {
+          return `${params.name}<br/>热度: ${params.value[2]}<br/>地点: ${params.data.location}`;
         }
       },
       visualMap: {
@@ -187,17 +153,14 @@ export class PageAssociation implements AfterViewInit {
         range: '2023-06',
         itemStyle: {
           borderWidth: 1,
-          borderColor: '#e8e0d0',
-          color: '#f9f6f0'
+          borderColor: '#e8e0d0'
         },
         dayLabel: {
-          color: '#8B4513',
-          fontSize: 12
+          color: '#8B4513'
         },
         monthLabel: {
           color: '#8B4513',
-          fontWeight: 'bold',
-          fontSize: 14
+          fontWeight: 'bold'
         },
         yearLabel: { show: false }
       },
@@ -214,222 +177,41 @@ export class PageAssociation implements AfterViewInit {
           ['2023-06-25', 55, '智慧旅游研讨会', '上饶'],
           ['2023-06-28', 95, '年度学术大会', '南昌']
         ].map((item) => ({
-          value: [item[0], item[1]],//日期,热度,事件,地点
-          event: item[2],
-          location:item[3]
-        })),
-        label: {
-          show: true,
-          formatter: (params: any) => {
-            return params.data.event.substring(0, 4);
-          },
-          color: '#333',
-          fontSize: 10
-        },
-        emphasis: {
-          itemStyle: {
-            shadowBlur: 10,
-            shadowColor: 'rgba(0, 0, 0, 0.5)'
-          }
-        }
+          value: [item[0], item[1], item[1], item[2], item[3]]
+        }))
       }
-    };
+    });
     
-    academicCalendar.setOption(option);
     window.addEventListener('resize', () => academicCalendar.resize());
   }
 
-  /**
-   * 搜索章程内容
-   * @param keyword 搜索关键词
-   */
-  searchConstitution(keyword: string): void {
-    if (!keyword.trim()) {
-      this.clearSearch();
-      return;
-    }
-    
-    this.searchTerm = keyword;
-    this.clearHighlights();
-    
-    const content = this.constitutionContent.nativeElement;
-    const textNodes = this.getTextNodes(content);
-    const regex = new RegExp(this.escapeRegExp(keyword), 'gi');
-    
-    let count = 0;
-    this.highlightElements = [];
-    
-    textNodes.forEach(node => {
-      if (node.nodeValue !== null) {
-        const textContent = node.nodeValue;
-        const matches = textContent.match(regex);
-
-      if (matches) {
-        count += matches.length;
-        const span = document.createElement('span');
-        span.className = 'search-highlight';
-        span.innerHTML = node.nodeValue.replace(regex, match => 
-          `<span class="highlight" tabindex="0">${match}</span>`
+  highlightSearch(searchTerm: string): void {
+    if (searchTerm) {
+      const content = document.querySelector('.constitution-content');
+      if (content) {
+        const text = content.innerHTML;
+        const regex = new RegExp(searchTerm, 'gi');
+        const highlighted = text.replace(regex, match => 
+          `<span class="highlight">${match}</span>`
         );
-        
-        // 替换原始节点
-        const parent = node.parentNode;
-        if (parent) {
-          parent.replaceChild(span, node);
-        }
-        
-        // 收集所有高亮元素并添加事件监听
-        const highlights = span.querySelectorAll('.highlight');
-        highlights.forEach((el, index) => {
-          const htmlEl = el as HTMLElement;
-          this.highlightElements.push(htmlEl);
-          
-          // 添加键盘导航支持
-          htmlEl.addEventListener('keydown', (e) => {
-            if (e.key === 'Enter') {
-              this.navigateToHighlight(index);
-            }
-          });
-        });
-      }
+        content.innerHTML = highlighted;
       }
-    });
-    
-    this.highlightCount = count;
-    this.currentHighlightIndex = -1;
-    
-    if (count > 0) {
-      this.navigateToHighlight(0); // 自动跳转到第一个匹配项
     }
   }
 
-  /**
-   * 导航到指定高亮项
-   * @param index 高亮项索引
-   */
-  private navigateToHighlight(index: number): void {
-    if (index < 0 || index >= this.highlightCount) return;
-    
-    // 清除当前高亮激活状态
-    if (this.currentHighlightIndex >= 0) {
-      this.highlightElements[this.currentHighlightIndex].classList.remove('active-highlight');
-    }
-    
-    this.currentHighlightIndex = index;
-    const currentElement = this.highlightElements[this.currentHighlightIndex];
-    
-    // 设置新激活项
-    currentElement.classList.add('active-highlight');
-    currentElement.focus();
-    
-    // 滚动到视图
-    currentElement.scrollIntoView({ 
-      behavior: 'smooth', 
-      block: 'center',
-      inline: 'nearest'
-    });
-  }
-
-  /**
-   * 导航高亮项
-   * @param direction 方向 (1: 下一个, -1: 上一个)
-   */
-  navigateHighlight(direction: number): void {
-    if (this.highlightCount === 0) return;
-    
-    let newIndex = this.currentHighlightIndex + direction;
-    
-    // 循环导航
-    if (newIndex < 0) newIndex = this.highlightCount - 1;
-    if (newIndex >= this.highlightCount) newIndex = 0;
-    
-    this.navigateToHighlight(newIndex);
-  }
-
-  /**
-   * 清除搜索
-   */
-  clearSearch(): void {
-    this.searchTerm = '';
-    this.highlightCount = 0;
-    this.currentHighlightIndex = -1;
-    this.clearHighlights();
-  }
-
-  /**
-   * 清除所有高亮
-   */
-  private clearHighlights(): void {
-    if (!this.constitutionContent?.nativeElement) return;
-    
-    const content = this.constitutionContent.nativeElement;
-    const highlights: NodeListOf<HTMLElement> = content.querySelectorAll('.search-highlight');
-    
-    highlights.forEach(highlight => {
-      // 确保有子节点再执行替换
-      if (highlight.childNodes.length > 0) {
-      highlight.replaceWith(...Array.from(highlight.childNodes));
-    } else {
-      // 如果没有子节点,直接替换为文本内容
-      highlight.replaceWith(highlight.textContent || '');
-    }
-    });
-    
-    this.highlightElements = [];
-
-    // 重置搜索状态
-  this.highlightCount = 0;
-  this.currentHighlightIndex = -1;
-  }
-
-  /**
-   * 获取所有文本节点
-   * @param node 起始节点
-   * @returns 文本节点数组
-   */
-  private getTextNodes(node: Node): Node[] {
-    let textNodes: Node[] = [];
-    
-    if (node.nodeType === Node.TEXT_NODE) {
-      textNodes.push(node);
-    } else {
-      const children = node.childNodes;
-      for (let i = 0; i < children.length; i++) {
-        textNodes = textNodes.concat(this.getTextNodes(children[i]));
-      }
-    }
-    
-    return textNodes.filter(n => n.nodeValue?.trim().length ?? 0 > 0);
-  }
-
-  /**
-   * 转义正则表达式特殊字符
-   * @param string 原始字符串
-   * @returns 转义后的字符串
-   */
-  private escapeRegExp(string: string): string {
-    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
-  }
-
-  /**
-   * 高亮版本差异
-   * @param event 鼠标事件
-   * @param type 高亮类型 ('diff' | 'new')
-   */
   highlightVersion(event: MouseEvent, type: 'diff' | 'new'): void {
-    const elements = document.querySelectorAll(`.${type}-highlight`) as NodeListOf<HTMLElement>;
-    
+    const elements = document.querySelectorAll(`.${type}-highlight`);
     elements.forEach(el => {
       el.classList.add('active-highlight');
     });
     
-    const mouseUpHandler = () => {
+    const handler = () => {
       elements.forEach(el => {
         el.classList.remove('active-highlight');
       });
-      document.removeEventListener('mouseup', mouseUpHandler);
+      document.removeEventListener('mouseup', handler);
     };
     
-    document.addEventListener('mouseup', mouseUpHandler);
+    document.addEventListener('mouseup', handler);
   }
 }

+ 20 - 20
travel-web/src/modules/shared/nav-pc-top-menu/nav-pc-top-menu.ts

@@ -45,15 +45,15 @@ export class NavPcTopMenu implements OnInit {
     
     // 尝试获取当前用户
     try {
-      const currentUser = await this.user.current();
+      const currentUser = await this.user.current();//尝试获取当前登录用户
       if (currentUser && this.user.id) {
-        this.user = currentUser;
-        this.generateUserInitials();
+        this.user = currentUser;//登陆状态有效,保存用户信息
+        this.generateUserInitials();//生成头像首字母
       }
     } catch (error) {
       console.error('获取用户信息失败:', error);
     } finally {
-      this.isLoading = false;
+      this.isLoading = false;//关闭加载态
     }
   }
 
@@ -87,24 +87,24 @@ export class NavPcTopMenu implements OnInit {
   }
 
   async login() {
-    if (!this.loginUsername || !this.loginPassword) {
+    if (!this.loginUsername || !this.loginPassword) {//检验表单必填项
       return;
     }
     
-    this.isLoggingIn = true;
+    this.isLoggingIn = true; // 显示“登录中”加载态
     
     try {
       const loggedInUser = await this.user.login(this.loginUsername, this.loginPassword);
       if (loggedInUser) {
-        this.user = loggedInUser;
-        this.generateUserInitials();
-        this.closeAuthModal();
+        this.user = loggedInUser;// 保存登录后的用户信息
+        this.generateUserInitials();// 更新头像首字母
+        this.closeAuthModal();// 关闭模态框
       }
     } catch (error) {
       console.error('登录失败:', error);
       // 实际应用中应显示错误提示
     } finally {
-      this.isLoggingIn = false;
+      this.isLoggingIn = false;// 关闭加载态
     }
   }
 
@@ -115,15 +115,15 @@ export class NavPcTopMenu implements OnInit {
     
     if (this.registerPassword !== this.registerPasswordConfirm) {
       // 实际应用中应显示错误提示
-      return;
+      return;// 校验密码一致
     }
     
     if (!this.acceptTerms) {
       // 实际应用中应显示错误提示
-      return;
+      return; // 校验协议勾选
     }
     
-    this.isRegistering = true;
+    this.isRegistering = true;// 显示“注册中”加载态
     
     try {
       const additionalData = {
@@ -138,9 +138,9 @@ export class NavPcTopMenu implements OnInit {
       );
       
       if (newUser) {
-        this.user = newUser;
-        this.generateUserInitials();
-        this.closeAuthModal();
+        this.user = newUser;// 保存注册后的用户信息
+        this.generateUserInitials();// 更新头像首字母
+        this.closeAuthModal();// 关闭模态框
       }
     } catch (error) {
       console.error('注册失败:', error);
@@ -152,12 +152,12 @@ export class NavPcTopMenu implements OnInit {
 
   async logout() {
     try {
-      await this.user.logout();
-      this.user = null;
+      await this.user.logout();// 调用后端退出接口
+      this.user = null; // 清除前端用户信息
       this.userAvatar = null;
       this.userInitials = '';
-      this.showMenu = false;
-      this.router.navigate(['/home']);
+      this.showMenu = false; // 关闭用户菜单
+      this.router.navigate(['/home']); // 跳转到首页
     } catch (error) {
       console.error('退出登录失败:', error);
     }