Browse Source

修改数据库

yujiahui 8 months ago
parent
commit
69c66f19b7
20 changed files with 710 additions and 69 deletions
  1. 0 0
      myapp/ node-cache/_cacache/content-v2/sha512/42/f2/3b5c80432bb87bbb916a5539b990336240aef6bcc60144e482e3215066b00e795a6f9010a374bc147ce5695eaaaea1720d5790dbd6f5acaaa9368d38e49c
  2. 0 0
      myapp/ node-cache/_cacache/content-v2/sha512/95/32/96ae0b2890f7c194bdbe0a17f53593781e0458e8a86dcfb431b199e9f76ad5e99690d8fb29a10b8f353cbb04f6d5fb65b8bdfa9eb516771e58f8e44d3d41
  3. 0 0
      myapp/ node-cache/_cacache/content-v2/sha512/ad/b4/a1bf8eef0dbba0ef5bae91aec581b9d20b6d83bb19ac80d4b6f6bcea99865051366aa707bf2dd83ed9ee5e964693421d3db28c0662b9824d99650f75ce97
  4. 0 0
      myapp/ node-cache/_cacache/content-v2/sha512/c8/dc/bbba87850f405ce1743074dc2f5cd44f73d1d35a5111d793264c402dd97a10d08ed13f214e197054882b7c623bb79ac57ff97716a3dbf58e91193eac2949
  5. 2 0
      myapp/ node-cache/_cacache/index-v5/17/da/a1ba1cd524d4d721da59dba521388d4673ee2ac9b88f30b64875eea922d1
  6. 2 0
      myapp/ node-cache/_cacache/index-v5/40/99/e6bca5ef42ef240f89854270c4120e89d6556fbcada37179f7d76ae96fc4
  7. 4 0
      myapp/ node-cache/_cacache/index-v5/53/40/fef6cf5e78dfb4314d73857409ae192ffe6f1bb12d852a0880b8493f7c73
  8. 4 0
      myapp/ node-cache/_cacache/index-v5/f5/ad/a3f36705b860b4ec9d888d3cc380beaff18a1f8e7d21e8ea7022255ff268
  9. 0 0
      myapp/ node-cache/_cacache/tmp/38168299
  10. 0 0
      myapp/ node-cache/_cacache/tmp/5392b10c
  11. 0 0
      myapp/ node-cache/_cacache/tmp/aaa863f5
  12. 0 0
      myapp/ node-cache/_cacache/tmp/e50b45c2
  13. 0 0
      myapp/ node-cache/_cacache/tmp/f27aa0a7
  14. 16 0
      myapp/node-global/parse-dashboard
  15. 17 0
      myapp/node-global/parse-dashboard.cmd
  16. 28 0
      myapp/node-global/parse-dashboard.ps1
  17. 64 35
      myapp/src/app/tab2/tab2.page.html
  18. 111 32
      myapp/src/app/tab2/tab2.page.scss
  19. 39 2
      myapp/src/app/tab2/tab2.page.ts
  20. 423 0
      myapp/src/lib/ncloud.ts

File diff suppressed because it is too large
+ 0 - 0
myapp/ node-cache/_cacache/content-v2/sha512/42/f2/3b5c80432bb87bbb916a5539b990336240aef6bcc60144e482e3215066b00e795a6f9010a374bc147ce5695eaaaea1720d5790dbd6f5acaaa9368d38e49c


File diff suppressed because it is too large
+ 0 - 0
myapp/ node-cache/_cacache/content-v2/sha512/95/32/96ae0b2890f7c194bdbe0a17f53593781e0458e8a86dcfb431b199e9f76ad5e99690d8fb29a10b8f353cbb04f6d5fb65b8bdfa9eb516771e58f8e44d3d41


File diff suppressed because it is too large
+ 0 - 0
myapp/ node-cache/_cacache/content-v2/sha512/ad/b4/a1bf8eef0dbba0ef5bae91aec581b9d20b6d83bb19ac80d4b6f6bcea99865051366aa707bf2dd83ed9ee5e964693421d3db28c0662b9824d99650f75ce97


File diff suppressed because it is too large
+ 0 - 0
myapp/ node-cache/_cacache/content-v2/sha512/c8/dc/bbba87850f405ce1743074dc2f5cd44f73d1d35a5111d793264c402dd97a10d08ed13f214e197054882b7c623bb79ac57ff97716a3dbf58e91193eac2949


+ 2 - 0
myapp/ node-cache/_cacache/index-v5/17/da/a1ba1cd524d4d721da59dba521388d4673ee2ac9b88f30b64875eea922d1

@@ -0,0 +1,2 @@
+
+31b2159e230e5125d912561309df23a90ca49c4a	{"key":"make-fetch-happen:request-cache:https://registry.npmmirror.com/@angular-builders%2fcustom-webpack","integrity":"sha512-rbShv47vDbug71uuka7FgbnSC22DuxmsgNS29rzqmYZQUTZqpwe/Ldg+2e5elkaTQh09sowGYrmCTZllD3XOlw==","time":1748508224401,"size":358010,"metadata":{"time":1748508223667,"url":"https://registry.npmmirror.com/@angular-builders%2fcustom-webpack","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json; charset=utf-8","date":"Thu, 29 May 2025 08:43:43 GMT","etag":"W/\"574fe702fcdb8a3787bcb1807c6b27806ab87810\"","vary":"Origin, Accept, Accept-Encoding"},"options":{"compress":true}}}

+ 2 - 0
myapp/ node-cache/_cacache/index-v5/40/99/e6bca5ef42ef240f89854270c4120e89d6556fbcada37179f7d76ae96fc4

@@ -0,0 +1,2 @@
+
+00516c49c54e8aa68fe2e16bf24f374c57f052e4	{"key":"make-fetch-happen:request-cache:https://registry.npmmirror.com/karma-jasmine-html-reporter","integrity":"sha512-lTKWrgsokPfBlL2+Chf1NZN4HgRY6Khtz7QxsZnp92rV6ZaQ2PspoQuPNTy7BPbV+2W4vfqetRZ3Hlj45E09QQ==","time":1748508218395,"size":47727,"metadata":{"time":1748508218380,"url":"https://registry.npmmirror.com/karma-jasmine-html-reporter","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json; charset=utf-8","date":"Thu, 29 May 2025 08:43:38 GMT","etag":"W/\"c80a7da4d688b9444e933bdf1f6e2c79dc9ef119\"","vary":"Origin, Accept, Accept-Encoding"},"options":{"compress":true}}}

+ 4 - 0
myapp/ node-cache/_cacache/index-v5/53/40/fef6cf5e78dfb4314d73857409ae192ffe6f1bb12d852a0880b8493f7c73

@@ -0,0 +1,4 @@
+
+3047536e7e5ea3b52685eb8c52f4f8640675a7b0	{"key":"make-fetch-happen:request-cache:https://registry.npmmirror.com/@angular-eslint%2feslint-plugin","integrity":"sha512-yNy7uoeFD0Bc4XQwdNwvXNRPc9HTWlER15MmTEAt2XoQ0I7RPyFOGXBUiCt8Yju3msV/+XcWo9v1jpEZPqwpSQ==","time":1748508218652,"size":2100304,"metadata":{"time":1748508218493,"url":"https://registry.npmmirror.com/@angular-eslint%2feslint-plugin","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json; charset=utf-8","date":"Thu, 29 May 2025 08:43:38 GMT","etag":"W/\"a371648cae6a2476da85dfe2c75ecae55ee90821\"","vary":"Origin, Accept, Accept-Encoding"},"options":{"compress":true}}}
+9af00487aa93533e194b369b4365783a9a7198fa	{"key":"make-fetch-happen:request-cache:https://registry.npmmirror.com/@angular-eslint%2feslint-plugin","integrity":"sha512-yNy7uoeFD0Bc4XQwdNwvXNRPc9HTWlER15MmTEAt2XoQ0I7RPyFOGXBUiCt8Yju3msV/+XcWo9v1jpEZPqwpSQ==","time":1748508223268,"size":2100304,"metadata":{"time":1748508223267,"url":"https://registry.npmmirror.com/@angular-eslint%2feslint-plugin","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","date":"Thu, 29 May 2025 08:43:43 GMT","etag":"W/\"a371648cae6a2476da85dfe2c75ecae55ee90821\"","vary":"Origin, Accept, Accept-Encoding","content-encoding":"gzip","content-type":"application/json; charset=utf-8"},"options":{"compress":true}}}
+f5f515660aebecee56b798111bb3667223f69e3f	{"key":"make-fetch-happen:request-cache:https://registry.npmmirror.com/@angular-eslint%2feslint-plugin","integrity":"sha512-yNy7uoeFD0Bc4XQwdNwvXNRPc9HTWlER15MmTEAt2XoQ0I7RPyFOGXBUiCt8Yju3msV/+XcWo9v1jpEZPqwpSQ==","time":1748508229073,"size":2100304,"metadata":{"time":1748508229073,"url":"https://registry.npmmirror.com/@angular-eslint%2feslint-plugin","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","date":"Thu, 29 May 2025 08:43:43 GMT","etag":"W/\"a371648cae6a2476da85dfe2c75ecae55ee90821\"","vary":"Origin, Accept, Accept-Encoding","content-encoding":"gzip","content-type":"application/json; charset=utf-8"},"options":{"compress":true}}}

+ 4 - 0
myapp/ node-cache/_cacache/index-v5/f5/ad/a3f36705b860b4ec9d888d3cc380beaff18a1f8e7d21e8ea7022255ff268

@@ -0,0 +1,4 @@
+
+6905d41bfb1c4d5d829d2cc81bab58f13ff834b7	{"key":"make-fetch-happen:request-cache:https://registry.npmmirror.com/@angular-eslint%2ftemplate-parser","integrity":"sha512-QvI7XIBDK7h7u5FqVTm5kDNiQK72vMYBROSC4yFQZrAOeVpvkBCjdLwUfOVpXqquoXINV5Db1vWsqqk2jTjknA==","time":1748508232668,"size":1964257,"metadata":{"time":1748508230471,"url":"https://registry.npmmirror.com/@angular-eslint%2ftemplate-parser","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json; charset=utf-8","date":"Thu, 29 May 2025 08:43:50 GMT","etag":"W/\"4b4af961d09ca2506b69412c992b94618451004b\"","vary":"Origin, Accept, Accept-Encoding"},"options":{"compress":true}}}
+7e9237bbe5af3e4551f66dbcb7659c85f1bc3021	{"key":"make-fetch-happen:request-cache:https://registry.npmmirror.com/@angular-eslint%2ftemplate-parser","integrity":"sha512-QvI7XIBDK7h7u5FqVTm5kDNiQK72vMYBROSC4yFQZrAOeVpvkBCjdLwUfOVpXqquoXINV5Db1vWsqqk2jTjknA==","time":1748508237444,"size":1964257,"metadata":{"time":1748508237443,"url":"https://registry.npmmirror.com/@angular-eslint%2ftemplate-parser","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","date":"Thu, 29 May 2025 08:43:50 GMT","etag":"W/\"4b4af961d09ca2506b69412c992b94618451004b\"","vary":"Origin, Accept, Accept-Encoding","content-encoding":"gzip","content-type":"application/json; charset=utf-8"},"options":{"compress":true}}}
+3cb304d5f91a83bfecf51113e6aa98b7c1924a33	{"key":"make-fetch-happen:request-cache:https://registry.npmmirror.com/@angular-eslint%2ftemplate-parser","integrity":"sha512-QvI7XIBDK7h7u5FqVTm5kDNiQK72vMYBROSC4yFQZrAOeVpvkBCjdLwUfOVpXqquoXINV5Db1vWsqqk2jTjknA==","time":1748508395324,"size":1964257,"metadata":{"time":1748508235060,"url":"https://registry.npmmirror.com/@angular-eslint%2ftemplate-parser","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json; charset=utf-8","date":"Thu, 29 May 2025 08:43:50 GMT","etag":"W/\"4b4af961d09ca2506b69412c992b94618451004b\"","vary":"Origin, Accept, Accept-Encoding"},"options":{"compress":true}}}

File diff suppressed because it is too large
+ 0 - 0
myapp/ node-cache/_cacache/tmp/38168299


File diff suppressed because it is too large
+ 0 - 0
myapp/ node-cache/_cacache/tmp/5392b10c


File diff suppressed because it is too large
+ 0 - 0
myapp/ node-cache/_cacache/tmp/aaa863f5


File diff suppressed because it is too large
+ 0 - 0
myapp/ node-cache/_cacache/tmp/e50b45c2


File diff suppressed because it is too large
+ 0 - 0
myapp/ node-cache/_cacache/tmp/f27aa0a7


+ 16 - 0
myapp/node-global/parse-dashboard

@@ -0,0 +1,16 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+    *CYGWIN*|*MINGW*|*MSYS*)
+        if command -v cygpath > /dev/null 2>&1; then
+            basedir=`cygpath -w "$basedir"`
+        fi
+    ;;
+esac
+
+if [ -x "$basedir/node" ]; then
+  exec "$basedir/node"  "$basedir/node_modules/parse-dashboard/bin/parse-dashboard" "$@"
+else 
+  exec node  "$basedir/node_modules/parse-dashboard/bin/parse-dashboard" "$@"
+fi

+ 17 - 0
myapp/node-global/parse-dashboard.cmd

@@ -0,0 +1,17 @@
+@ECHO off
+GOTO start
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
+:start
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+  SET "_prog=%dp0%\node.exe"
+) ELSE (
+  SET "_prog=node"
+  SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%\node_modules\parse-dashboard\bin\parse-dashboard" %*

+ 28 - 0
myapp/node-global/parse-dashboard.ps1

@@ -0,0 +1,28 @@
+#!/usr/bin/env pwsh
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
+
+$exe=""
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
+  # Fix case when both the Windows and Linux builds of Node
+  # are installed in the same directory
+  $exe=".exe"
+}
+$ret=0
+if (Test-Path "$basedir/node$exe") {
+  # Support pipeline input
+  if ($MyInvocation.ExpectingInput) {
+    $input | & "$basedir/node$exe"  "$basedir/node_modules/parse-dashboard/bin/parse-dashboard" $args
+  } else {
+    & "$basedir/node$exe"  "$basedir/node_modules/parse-dashboard/bin/parse-dashboard" $args
+  }
+  $ret=$LASTEXITCODE
+} else {
+  # Support pipeline input
+  if ($MyInvocation.ExpectingInput) {
+    $input | & "node$exe"  "$basedir/node_modules/parse-dashboard/bin/parse-dashboard" $args
+  } else {
+    & "node$exe"  "$basedir/node_modules/parse-dashboard/bin/parse-dashboard" $args
+  }
+  $ret=$LASTEXITCODE
+}
+exit $ret

+ 64 - 35
myapp/src/app/tab2/tab2.page.html

@@ -8,44 +8,73 @@
 </ion-header>
 
 <ion-content [fullscreen]="true">
-<!-- 在HTML模板文件中 -->
-<ion-card class="tour-guide-card">
-  <!-- 卡片头部 -->
-  <div class="card-header">
-    <!-- 圆形头像 -->
-    <div class="avatar-container">
-      <img src="/assets/daoyou.jpeg" alt="规划师头像" class="guide-avatar">
-      <div class="online-status"></div>
-    </div>
-    
-    <!-- 基本信息 -->
-    <div class="guide-info">
-      <h2>林小漫</h2>
-      <p class="specialty">资深旅游规划师 · 6年经验</p>
-      <div class="rating">
-        <ion-icon name="star" color="warning"></ion-icon>
-        <span>4.9</span>
-        <span class="reviews">(128条评价)</span>
+  <!-- 导游信息卡片 -->
+  <ion-card class="tour-guide-card">
+    <div class="card-header">
+      <div class="avatar-container">
+        <img src="/assets/daoyou.jpeg" alt="规划师头像" class="guide-avatar">
+        <div class="online-status"></div>
+      </div>
+      
+      <div class="guide-info">
+        <h2>林小漫</h2>
+        <p class="specialty">资深旅游规划师 · 6年经验</p>
+        <div class="rating">
+          <ion-icon name="star" color="warning"></ion-icon>
+          <span>4.9</span>
+          <span class="reviews">(128条评价)</span>
+        </div>
       </div>
     </div>
-  </div>
 
-  <!-- 服务标签 -->
-  <div class="tags">
-    <span class="tag">旅行定制</span>
-    <span class="tag">历史专家</span>
-    <span class="tag">摄影达人</span>
-  </div>
+    <div class="tags">
+      <span class="tag">旅行定制</span>
+      <span class="tag">历史专家</span>
+      <span class="tag">摄影达人</span>
+    </div>
+
+    <p class="bio">
+      我是资深旅游规划师,6年高端定制旅行经验,足迹遍布60+国家,熟悉各景点背后的文化故事,能带您发现常规路线外的独特体验,擅长为游客定制个性化行程。
+    </p>
 
-  <!-- 个人简介 -->
-  <p class="bio">
-    我是资深旅游规划师,6年高端定制旅行经验,足迹遍布60+国家,熟悉各景点背后的文化故事,能带您发现常规路线外的独特体验,擅长为游客定制个性化行程。
-  </p>
+    <ion-button (click)="openConsult()" expand="block" class="consult-btn" shape="round">
+      <ion-icon name="chatbubble-ellipses" slot="start"></ion-icon>
+      立即咨询
+    </ion-button>
+    <ion-button (click)="loadConsult()" expand="block" shape="round">
+      <ion-icon name="time-outline" slot="start"></ion-icon>
+      查看历史记录
+    </ion-button>
+  </ion-card>
 
-  <!-- 咨询按钮 -->
-  <ion-button (click)="openConsult()" expand="block" class="consult-btn" shape="round">
-    <ion-icon name="chatbubble-ellipses" slot="start"></ion-icon>
-    立即咨询
-  </ion-button>
-</ion-card>
+  <!-- 历史咨询记录 - 修改后的布局 -->
+  @if (consultList?.length) {
+    <ion-card class="history-card">
+      <ion-card-header>
+        <ion-card-title>历史咨询记录</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <div class="consult-list">
+          @for (consult of consultList; track consult) {
+            <div class="consult-item"  (click)="openConsult(consult.get('chatId'))">
+              <div class="avatar-wrapper">
+                @if (consult.get('avatar')) {
+                  <img [src]="consult.get('avatar')" alt="用户头像" class="user-avatar">
+                } @else {
+                  <div class="default-avatar">
+                    <ion-icon name="person-outline"></ion-icon>
+                  </div>
+                }
+              </div>
+              <div class="text-wrapper">
+                <div class="user-name">{{ consult.get('name') || '无名氏' }}</div>
+                <div class="consult-time">{{ consult.updatedAt}}</div>
+              </div>
+              <ion-icon name="chevron-forward" class="arrow-icon"></ion-icon>
+            </div>
+          }
+        </div>
+      </ion-card-content>
+    </ion-card>
+  }
 </ion-content>

+ 111 - 32
myapp/src/app/tab2/tab2.page.scss

@@ -1,65 +1,68 @@
-/* 在对应的SCSS文件中 */
+/* tab2.page.scss */
 .tour-guide-card {
-  border-radius: 16px;
-  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
-  padding: 16px;
   margin: 16px;
+  padding: 16px;
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
   
   .card-header {
     display: flex;
     align-items: center;
-    margin-bottom: 12px;
+    margin-bottom: 16px;
     
     .avatar-container {
       position: relative;
       margin-right: 16px;
       
       .guide-avatar {
-        width: 80px;
-        height: 80px;
+        width: 64px;
+        height: 64px;
         border-radius: 50%;
         object-fit: cover;
-        border: 3px solid var(--ion-color-primary);
+        border: 2px solid #fff;
+        box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
       }
       
       .online-status {
         position: absolute;
-        bottom: 5px;
-        right: 5px;
-        width: 15px;
-        height: 15px;
-        background-color: #4CAF50;
+        bottom: 4px;
+        right: 4px;
+        width: 14px;
+        height: 14px;
+        background-color: #4caf50;
         border-radius: 50%;
         border: 2px solid white;
       }
     }
     
     .guide-info {
+      flex: 1;
+      
       h2 {
         margin: 0;
         font-size: 1.3rem;
-        font-weight: bold;
+        font-weight: 600;
+        color: #333;
       }
       
       .specialty {
-        color: var(--ion-color-medium);
-        margin: 4px 0;
+        margin: 6px 0;
         font-size: 0.9rem;
+        color: #666;
       }
       
       .rating {
         display: flex;
         align-items: center;
-        font-size: 0.9rem;
+        font-size: 0.95rem;
         
         ion-icon {
           margin-right: 4px;
         }
         
         .reviews {
-          color: var(--ion-color-medium);
-          margin-left: 6px;
-          font-size: 0.8rem;
+          margin-left: 8px;
+          color: #888;
         }
       }
     }
@@ -69,29 +72,105 @@
     display: flex;
     flex-wrap: wrap;
     gap: 8px;
-    margin-bottom: 12px;
+    margin-bottom: 16px;
     
     .tag {
-      background: rgba(var(--ion-color-primary-rgb), 0.1);
-      color: var(--ion-color-primary);
-      padding: 4px 10px;
+      padding: 6px 12px;
+      background-color: #f0f8ff;
       border-radius: 16px;
-      font-size: 0.8rem;
+      font-size: 0.85rem;
+      color: #3880ff;
+      font-weight: 500;
     }
   }
   
   .bio {
-    color: var(--ion-color-medium);
+    margin: 0 0 20px 0;
+    color: #444;
+    line-height: 1.6;
     font-size: 0.95rem;
-    line-height: 1.4;
-    margin-bottom: 20px;
   }
   
   .consult-btn {
-    --border-radius: 24px;
-    --padding-top: 12px;
-    --padding-bottom: 12px;
-    font-weight: bold;
     margin-top: 8px;
+    --background: #3880ff;
+    --background-activated: #3171e0;
+    font-weight: 500;
+  }
+}
+
+.history-card {
+  margin: 16px;
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+  
+  ion-card-header {
+    padding-bottom: 8px;
+    
+    ion-card-title {
+      font-size: 1.2rem;
+      font-weight: 600;
+    }
+  }
+  
+  .consult-list {
+    .consult-item {
+      display: flex;
+      align-items: center;
+      padding: 12px 0;
+      border-bottom: 1px solid #f0f0f0;
+      
+      &:last-child {
+        border-bottom: none;
+      }
+      
+      .avatar-wrapper {
+        margin-right: 14px;
+        
+        .user-avatar, .default-avatar {
+          width: 48px;
+          height: 48px;
+          border-radius: 50%;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+        }
+        
+        .user-avatar {
+          object-fit: cover;
+          background-color: #f5f5f5;
+        }
+        
+        .default-avatar {
+          background-color: #f0f8ff;
+          
+          ion-icon {
+            font-size: 24px;
+            color: #3880ff;
+          }
+        }
+      }
+      
+      .text-wrapper {
+        flex: 1;
+        
+        .user-name {
+          font-weight: 500;
+          font-size: 1rem;
+          color: #333;
+          margin-bottom: 4px;
+        }
+        
+        .consult-time {
+          font-size: 0.85rem;
+          color: #888;
+        }
+      }
+      
+      .arrow-icon {
+        color: #ccc;
+        font-size: 1.2rem;
+      }
+    }
   }
 }

+ 39 - 2
myapp/src/app/tab2/tab2.page.ts

@@ -5,6 +5,7 @@ import { ModalController } from '@ionic/angular/standalone';
 // 引用fmode-ng智能体组件
 import { ChatPanelOptions, FmChatModalInput, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
 import Parse from "parse";
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
 @Component({
   selector: 'app-tab2',
   templateUrl: 'tab2.page.html',
@@ -22,7 +23,7 @@ export class Tab2Page  {
     localStorage.setItem("company","E4KpGvTEto")
     let options:ChatPanelOptions = {
       roleId:"2DXJkRsjXK", // 预设,无需更改
-      // chatId:chatId, // 若存在,则恢复会话。若不存在,则开启新会话
+      chatId:chatId, // 若存在,则恢复会话。若不存在,则开启新会话
       onChatInit:(chat:FmodeChat)=>{
         console.log("onChatInit");
         console.log("Chat类",chat);
@@ -84,6 +85,7 @@ export class Tab2Page  {
 "别担心迷路,所有意外都是独家旅行剧本!"
 
 "您刚才摸的这块砖,崇祯皇帝可能也摸过哦~"
+请您模仿一个正常旅行规划师对话,语言简短不需要长篇大论
 `);
         // 对话灵感分类
         let promptCates = [
@@ -181,11 +183,46 @@ export class Tab2Page  {
           }
         }
       },
-      onChatSaved:(chat:FmodeChat)=>{
+      onChatSaved:async (chat:FmodeChat)=>{
         // chat?.chatSession?.id 本次会话的 chatId
         console.log("onChatSaved",chat,chat?.chatSession,chat?.chatSession?.id)
+        let chatId = chat?.chatSession?.id;
+        console.log("chatId",chatId);
+
+        let query = new CloudQuery("TravelConsult");
+        let travelConsult=await query.get(chatId);
+        console.log("TravelConsult",travelConsult);
+
+        // 如果不存在,则新建对象
+        if (!travelConsult) {
+            travelConsult = new CloudObject("TravelConsult");
+        }
+        travelConsult.set({
+          chatId:chatId,
+          "messageList":chat.messageList,
+          "name":chat.role.get("name"),
+          "avatar":chat.role.get("avatar"),
+        })
+        console.log("trainingConsult",travelConsult);
+        travelConsult.save();
       }
     }
     openChatPanelModal(this.modalCtrl,options)
   }
+  consultList:Array<CloudObject>=[]
+  async loadConsult(){
+    let query = new CloudQuery("TravelConsult");
+    this.consultList=await query.find();
+    console.log("consultList",this.consultList);
+  }
+  openChat(){
+    let options:ChatPanelOptions = {
+      roleId:"2DXJkRsjXK", // 预设,无需更改
+      onChatSaved:(chat:FmodeChat)=>{
+        console.log("onChatSaved",chat,chat?.chatSession,chat?.chatSession?.id);
+  },
+}
+    openChatPanelModal(this.modalCtrl,options)
+  }
+  
 }

+ 423 - 0
myapp/src/lib/ncloud.ts

@@ -0,0 +1,423 @@
+// CloudObject.ts
+export class CloudObject {
+    className: string;
+    id: string |undefined = undefined;
+    createdAt:any;
+    updatedAt:any;
+    data: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt"].indexOf(key) > -1) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save() {
+        let method = "POST";
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}`;
+
+        // 更新
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        const body = JSON.stringify(this.data);
+        const response = await fetch(url, {
+            headers: {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
+            },
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        return this;
+    }
+
+    async destroy() {
+        if (!this.id) return;
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result) {
+            this.id = undefined;
+        }
+        return true;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    limit(arg0: number) {
+      throw new Error('Method not implemented.');
+    }
+    className: string;
+    queryParams: Record<string, any> = {where:{}};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    include(...fileds:string[]) {
+        this.queryParams["include"] = fileds;
+    }
+    greaterThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        if (!this.queryParams["where"]) this.queryParams["where"] = {};
+        this.queryParams["where"][key] = value;
+    }
+
+    async get(id: string) {
+        const url = `http://dev.fmode.cn:1337/parse/classes/${this.className}/${id}?`;
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        if (json) {
+            let existsObject = this.dataToObj(json)
+            return existsObject;
+        }
+        return null
+    }
+
+    async find():Promise<Array<CloudObject>> {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        let queryStr = ``
+        Object.keys(this.queryParams).forEach(key=>{
+            let paramStr = JSON.stringify(this.queryParams[key]);
+            if(key=="include"){
+                paramStr = this.queryParams[key]?.join(",")
+            }
+            if(queryStr) {
+                url += `${key}=${paramStr}`;
+            }else{
+                url += `&${key}=${paramStr}`;
+            }
+        })
+        // if (Object.keys(this.queryParams["where"]).length) {
+
+        // }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item:any)=>this.dataToObj(item))
+        return objList || [];
+    }
+
+
+    async first() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        if (Object.keys(this.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
+            url += `where=${whereStr}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+    }
+
+    dataToObj(exists:any):CloudObject{
+        let existsObject = new CloudObject(this.className);
+        existsObject.set(exists);
+        existsObject.id = exists.objectId;
+        existsObject.createdAt = exists.createdAt;
+        existsObject.updatedAt = exists.updatedAt;
+        return existsObject;
+    }
+}
+
+// CloudUser.ts
+export class CloudUser extends CloudObject {
+    constructor() {
+        super("_User"); // 假设用户类在Parse中是"_User"
+        // 读取用户缓存信息
+        let userCacheStr = localStorage.getItem("NCloud/dev/User")
+        if(userCacheStr){
+            let userData = JSON.parse(userCacheStr)
+            // 设置用户信息
+            this.id = userData?.objectId;
+            this.sessionToken = userData?.sessionToken;
+            this.data = userData; // 保存用户数据
+        }
+    }
+
+    sessionToken:string|null = ""
+    /** 获取当前用户信息 */
+    async current() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return null;
+        }
+        return this;
+        // const response = await fetch(`http://dev.fmode.cn:1337/parse/users/me`, {
+        //     headers: {
+        //         "x-parse-application-id": "dev",
+        //         "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
+        //     },
+        //     method: "GET"
+        // });
+
+        // const result = await response?.json();
+        // if (result?.error) {
+        //     console.error(result?.error);
+        //     return null;
+        // }
+        // return result;
+    }
+
+    /** 登录 */
+    async login(username: string, password: string):Promise<CloudUser|null> {
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/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);
+            return null;
+        }
+
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User",JSON.stringify(result))
+        return this;
+    }
+
+    /** 登出 */
+    async logout() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return;
+        }
+
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/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;
+    }
+    clearUserCache(){
+        // 清除用户信息
+        localStorage.removeItem("NCloud/dev/User")
+        this.id = undefined;
+        this.sessionToken = null;
+        this.data = {};
+    }
+
+    /** 注册 */
+    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+        const userData = {
+            username,
+            password,
+            ...additionalData // 合并额外的用户数据
+        };
+
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/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;
+    }
+
+    override async save() {
+        let method = "POST";
+        let url = `http://dev.fmode.cn:1337/parse/users`;
+
+        // 更新用户信息
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        let data:any = JSON.parse(JSON.stringify(this.data))
+        delete data.createdAt
+        delete data.updatedAt
+        delete data.ACL
+        delete data.objectId
+        const body = JSON.stringify(data);
+        let headersOptions:any = {
+            "content-type": "application/json;charset=UTF-8",
+            "x-parse-application-id": "dev",
+            "x-parse-session-token": this.sessionToken, // 添加sessionToken以进行身份验证
+        }
+        const response = await fetch(url, {
+            headers: headersOptions,
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        localStorage.setItem("NCloud/dev/User",JSON.stringify(this.data))
+        return this;
+    }
+}
+
+export class CloudApi{
+    async fetch(path:string,body:any,options?:{
+        method:string
+        body:any
+    }){
+
+        let reqOpts:any =  {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            method: options?.method || "POST",
+            mode: "cors",
+            credentials: "omit"
+        }
+        if(body||options?.body){
+            reqOpts.body = JSON.stringify(body || options?.body);
+            reqOpts.json = true;
+        }
+        let host = `http://dev.fmode.cn:1337`
+        // host = `http://127.0.0.1:1337`
+        let url = `${host}/api/`+path
+        console.log(url,reqOpts)
+        const response = await fetch(url,reqOpts);
+        let json = await response.json();
+        return json
+    }
+}

Some files were not shown because too many files changed in this diff