dangdang 1 месяц назад
Сommit
d464cdb4b3
53 измененных файлов с 5095 добавлено и 0 удалено
  1. 24 0
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 7 0
      Dockerfile
  4. 5 0
      README.md
  5. 66 0
      default.conf
  6. 7 0
      depoly.sh
  7. 17 0
      index.html
  8. 1607 0
      package-lock.json
  9. 31 0
      package.json
  10. BIN
      public/image0.jpg
  11. BIN
      public/image0.webp
  12. BIN
      public/image1.webp
  13. BIN
      public/image2.webp
  14. 1 0
      public/vite.svg
  15. 9 0
      src/App.vue
  16. BIN
      src/assets/WechatIMG221.jpg
  17. BIN
      src/assets/WechatIMG222.jpg
  18. BIN
      src/assets/banner1.jpg
  19. BIN
      src/assets/banner2.jpg
  20. BIN
      src/assets/banner3.jpg
  21. BIN
      src/assets/banner4.jpg
  22. BIN
      src/assets/head.jpg
  23. BIN
      src/assets/robot.jpg
  24. BIN
      src/assets/role.jpg
  25. BIN
      src/assets/role1.jpg
  26. BIN
      src/assets/role2.jpg
  27. BIN
      src/assets/role3.jpg
  28. 1 0
      src/assets/vue.svg
  29. 43 0
      src/components/HelloWorld.vue
  30. 51 0
      src/components/NavBarPage.vue
  31. 36 0
      src/hooks/useScroll.js
  32. 20 0
      src/main.js
  33. 66 0
      src/router/index.js
  34. 0 0
      src/style.css
  35. 458 0
      src/views/ChatTts.vue
  36. 287 0
      src/views/DetailView.vue
  37. 305 0
      src/views/HomeView.vue
  38. 259 0
      src/views/ResultView.vue
  39. 48 0
      src/views/components/BG.vue
  40. 353 0
      src/views/components/BottomArea.vue
  41. 126 0
      src/views/components/ChatList.vue
  42. 105 0
      src/views/demo/Asr.vue
  43. 457 0
      src/views/demo/AsrDemo.vue
  44. 55 0
      src/views/demo/Home.vue
  45. 51 0
      src/views/demo/Pic.vue
  46. 53 0
      src/views/demo/Sse.vue
  47. 151 0
      src/views/demo/Steam.vue
  48. 57 0
      src/views/demo/Tts.vue
  49. 92 0
      src/views/result/randarPage.vue
  50. 133 0
      src/views/result/selectPage.vue
  51. 18 0
      src/views/utils/api.js
  52. 46 0
      src/views/utils/request.js
  53. 47 0
      vite.config.js

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 7 - 0
Dockerfile

@@ -0,0 +1,7 @@
+FROM nginx
+
+COPY ./default.conf /etc/nginx/conf.d/default.conf
+
+COPY ./dist /usr/share/nginx/html
+
+EXPOSE 80

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
+# Vue 3 + Vite
+
+This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

+ 66 - 0
default.conf

@@ -0,0 +1,66 @@
+server {
+    listen       80;
+    listen  [::]:80;
+    server_name  localhost;
+
+    resolver 8.8.8.8;
+
+    #access_log  /var/log/nginx/host.access.log  main;
+
+    location / {
+        root   /usr/share/nginx/html;
+        index  index.html index.htm;
+    }
+
+    set $stg_token "C1qziFGlIv3tnCQxcFaStrLuZOO2ZZXjN7FB_G0WlrOLjclfObbSaXAKzl4RWwQBf_0Zhsm0CoVvdVsYMD18iM_LJrxtn7LHJJQuF9UoUuF3fvqOwrG4EF6Z4GahtxtQ2oeaPQBBNKlgVW1xUW7tkhEdXWqzDHPA_I_91Lczk0PI4guhx1c88Hst4-HI8pdMbiUdEJzj3d3a2W06Fa0XA9Q0taAwaRd1k9jUrDVyj9GfS84_SIgJF4SPjWVfsraV79ieb_StgRcUwZjbscGPMlifnJD6F00wwNbxG7AuCHbl3EtMfSed1vuVx3AsizIckwzIVSVRpOGw72cdAMui-I6es9Ozj2ITzSa5KgyXEpX4qCHF1VcCM1wlHLQ_5hLnJIi4r8NsnJPsxMYrTw";
+
+    set $prd_token "mVcexzY_mjtGAL5_exPlmAyfOJxuuEthWY1mk9tUFC_HwceY58uRZ2WDhz7-ttexCdUtFN8C7V636_jIq6fzaSfqIj8OQyhUPKPMa2eZjLlblT77ySqBt_lYM6iEAhrj7-raGmySMmkLS4Rqh651Ak2tqmUbjS64cqv5ofMsuadOCg1J-CtLFt7NeSoU4N3Kpm5MJ_4sOFBhQGfBym88dcwxosFl9LbvhpyleXFf6fOZkkOj0l2X8Nr2pfNjYs3_VOmCQxrxXh1XZ_a1v9qj5_rA9k9wGNNQfmr2JwJTUT4V9NwtNq94gNFt8C0J6MWKVE2eyr25Rke8tkKu3CGNNmspmEFpr6LavPlaWnWOIh9CRJ1cIDB70pg_JD2l0nPTkPbtaTQaIGTz";
+    
+    location /openapi-stg/ {
+        rewrite ^/openapi-stg/(.*)$ /$1 break;
+        proxy_pass https://fls-ai-stg-sit.pingan.com.cn/openapi$uri?token=$stg_token&channelId=ASP-TEST&sceneId=ASP-TEST&$args;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    }
+
+    location /openapi-prd/ {
+        rewrite ^/openapi-prd/(.*)$ /$1 break;
+        proxy_pass https://fls-ai.pingan.com.cn/openapi$uri?token=$prd_token&channelId=ASP-TEST&sceneId=ASP-TEST&$args;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    }
+
+    #error_page  404              /404.html;
+
+    # redirect server error pages to the static page /50x.html
+    #
+    error_page   500 502 503 504  /50x.html;
+    location = /50x.html {
+        root   /usr/share/nginx/html;
+    }
+
+    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
+    #
+    #location ~ \.php$ {
+    #    proxy_pass   http://127.0.0.1;
+    #}
+
+    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
+    #
+    #location ~ \.php$ {
+    #    root           html;
+    #    fastcgi_pass   127.0.0.1:9000;
+    #    fastcgi_index  index.php;
+    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
+    #    include        fastcgi_params;
+    #}
+
+    # deny access to .htaccess files, if Apache's document root
+    # concurs with nginx's one
+    #
+    #location ~ /\.ht {
+    #    deny  all;
+    #}
+}

+ 7 - 0
depoly.sh

@@ -0,0 +1,7 @@
+#/bin/bash
+
+npm run build
+
+docker build -t registry.chuckchen.top/test:video-test .
+
+docker push registry.chuckchen.top/test:video-test

+ 17 - 0
index.html

@@ -0,0 +1,17 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8" />
+  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+  <meta name="viewport"
+    content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no viewport-fit=cover" />
+  <title>Vite + Vue</title>
+</head>
+
+<body>
+  <div id="app"></div>
+  <script type="module" src="/src/main.js"></script>
+</body>
+
+</html>

+ 1607 - 0
package-lock.json

@@ -0,0 +1,1607 @@
+{
+  "name": "video-test",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "video-test",
+      "version": "0.0.0",
+      "dependencies": {
+        "@microsoft/fetch-event-source": "^2.0.1",
+        "axios": "^1.7.9",
+        "delay": "^6.0.0",
+        "echarts": "^5.5.1",
+        "fs": "^0.0.1-security",
+        "less": "^4.2.1",
+        "p-queue": "^8.0.1",
+        "recorder-core": "^1.3.24102001",
+        "vant": "^4.9.10",
+        "vconsole": "^3.15.1",
+        "vue": "^3.5.13",
+        "vue-router": "^4.0.13"
+      },
+      "devDependencies": {
+        "@vicons/material": "^0.13.0",
+        "@vicons/utils": "^0.1.4",
+        "@vitejs/plugin-vue": "^5.2.1",
+        "vite": "^6.0.1"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+      "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+      "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.26.3",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.3.tgz",
+      "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==",
+      "dependencies": {
+        "@babel/types": "^7.26.3"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.26.0",
+      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.0.tgz",
+      "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+      "dependencies": {
+        "regenerator-runtime": "^0.14.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.26.3",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.3.tgz",
+      "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.25.9",
+        "@babel/helper-validator-identifier": "^7.25.9"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@emotion/hash": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz",
+      "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+      "dev": true
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz",
+      "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz",
+      "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz",
+      "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz",
+      "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
+      "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz",
+      "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz",
+      "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz",
+      "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz",
+      "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz",
+      "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz",
+      "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz",
+      "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz",
+      "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz",
+      "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz",
+      "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz",
+      "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz",
+      "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz",
+      "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz",
+      "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz",
+      "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz",
+      "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz",
+      "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz",
+      "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz",
+      "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+    },
+    "node_modules/@microsoft/fetch-event-source": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
+      "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA=="
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
+      "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz",
+      "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz",
+      "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz",
+      "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz",
+      "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz",
+      "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz",
+      "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz",
+      "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz",
+      "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz",
+      "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz",
+      "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz",
+      "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz",
+      "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz",
+      "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
+      "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz",
+      "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz",
+      "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz",
+      "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz",
+      "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz",
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+      "dev": true
+    },
+    "node_modules/@types/node": {
+      "version": "22.10.2",
+      "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.10.2.tgz",
+      "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "undici-types": "~6.20.0"
+      }
+    },
+    "node_modules/@vant/popperjs": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.3.0.tgz",
+      "integrity": "sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw=="
+    },
+    "node_modules/@vant/use": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/@vant/use/-/use-1.6.0.tgz",
+      "integrity": "sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==",
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
+    },
+    "node_modules/@vicons/material": {
+      "version": "0.13.0",
+      "resolved": "https://registry.npmmirror.com/@vicons/material/-/material-0.13.0.tgz",
+      "integrity": "sha512-lKVxFNprM+CaBkUH3gt6VjIeiMsKQl2zARQMwTCZruQl2vRHzyeZiKeCflWS99CEfv2JzX/6y697smxlzyxcVw==",
+      "dev": true
+    },
+    "node_modules/@vicons/utils": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/@vicons/utils/-/utils-0.1.4.tgz",
+      "integrity": "sha512-OHI19qVNN6i+uPQ+Y3f2s0dUxwsYnOCcKBW7XOU4yXXO1aU3ZoKpblCc3+4N0qmgoJs5rWKRAaMisipqEXJwAg==",
+      "dev": true,
+      "dependencies": {
+        "@xicons/utils": "^0.1.4"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.6"
+      }
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
+      "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^5.0.0 || ^6.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
+      "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
+      "dependencies": {
+        "@babel/parser": "^7.25.3",
+        "@vue/shared": "3.5.13",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
+      "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
+      "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
+      "dependencies": {
+        "@babel/parser": "^7.25.3",
+        "@vue/compiler-core": "3.5.13",
+        "@vue/compiler-dom": "3.5.13",
+        "@vue/compiler-ssr": "3.5.13",
+        "@vue/shared": "3.5.13",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.11",
+        "postcss": "^8.4.48",
+        "source-map-js": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
+      "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz",
+      "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
+      "dependencies": {
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
+      "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
+      "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.13",
+        "@vue/runtime-core": "3.5.13",
+        "@vue/shared": "3.5.13",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
+      "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.13",
+        "@vue/shared": "3.5.13"
+      },
+      "peerDependencies": {
+        "vue": "3.5.13"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz",
+      "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="
+    },
+    "node_modules/@xicons/utils": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/@xicons/utils/-/utils-0.1.4.tgz",
+      "integrity": "sha512-uXxKDLz9abr80yJC05XSTq6wlyFcdW+N/1IYJkeHjzzXVc4VQ0sEYMoMMTjAH7HQBOyOkzOB4pf5NGF72lwa8Q==",
+      "dev": true,
+      "dependencies": {
+        "css-render": "^0.13.2"
+      }
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/axios": {
+      "version": "1.7.9",
+      "resolved": "https://registry.npmmirror.com/axios/-/axios-1.7.9.tgz",
+      "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/copy-anything": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz",
+      "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
+      "dependencies": {
+        "is-what": "^3.14.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mesqueeb"
+      }
+    },
+    "node_modules/copy-text-to-clipboard": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz",
+      "integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/core-js": {
+      "version": "3.39.0",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.39.0.tgz",
+      "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==",
+      "hasInstallScript": true,
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/core-js"
+      }
+    },
+    "node_modules/css-render": {
+      "version": "0.13.9",
+      "resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.13.9.tgz",
+      "integrity": "sha512-n3C4ZH59rveBrUlAD7n0Ze9/gUMKa4dlH1C9CWKpGcIHR/xRcIVXzBGy1iw8WWq2ySmn2/ZqOpySQNAK5Pb6sw==",
+      "dev": true,
+      "dependencies": {
+        "@emotion/hash": "~0.8.0",
+        "@types/node": "~14.14.31",
+        "csstype": "~3.0.5"
+      }
+    },
+    "node_modules/css-render/node_modules/@types/node": {
+      "version": "14.14.45",
+      "resolved": "https://registry.npmmirror.com/@types/node/-/node-14.14.45.tgz",
+      "integrity": "sha512-DssMqTV9UnnoxDWu959sDLZzfvqCF0qDNRjaWeYSui9xkFe61kKo4l1TWNTQONpuXEm+gLMRvdlzvNHBamzmEw==",
+      "dev": true
+    },
+    "node_modules/css-render/node_modules/csstype": {
+      "version": "3.0.11",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.0.11.tgz",
+      "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==",
+      "dev": true
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+    },
+    "node_modules/delay": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/delay/-/delay-6.0.0.tgz",
+      "integrity": "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==",
+      "engines": {
+        "node": ">=16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/echarts": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz",
+      "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "5.6.1"
+      }
+    },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/errno": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz",
+      "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
+      "optional": true,
+      "dependencies": {
+        "prr": "~1.0.1"
+      },
+      "bin": {
+        "errno": "cli.js"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.24.0.tgz",
+      "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.24.0",
+        "@esbuild/android-arm": "0.24.0",
+        "@esbuild/android-arm64": "0.24.0",
+        "@esbuild/android-x64": "0.24.0",
+        "@esbuild/darwin-arm64": "0.24.0",
+        "@esbuild/darwin-x64": "0.24.0",
+        "@esbuild/freebsd-arm64": "0.24.0",
+        "@esbuild/freebsd-x64": "0.24.0",
+        "@esbuild/linux-arm": "0.24.0",
+        "@esbuild/linux-arm64": "0.24.0",
+        "@esbuild/linux-ia32": "0.24.0",
+        "@esbuild/linux-loong64": "0.24.0",
+        "@esbuild/linux-mips64el": "0.24.0",
+        "@esbuild/linux-ppc64": "0.24.0",
+        "@esbuild/linux-riscv64": "0.24.0",
+        "@esbuild/linux-s390x": "0.24.0",
+        "@esbuild/linux-x64": "0.24.0",
+        "@esbuild/netbsd-x64": "0.24.0",
+        "@esbuild/openbsd-arm64": "0.24.0",
+        "@esbuild/openbsd-x64": "0.24.0",
+        "@esbuild/sunos-x64": "0.24.0",
+        "@esbuild/win32-arm64": "0.24.0",
+        "@esbuild/win32-ia32": "0.24.0",
+        "@esbuild/win32-x64": "0.24.0"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/eventemitter3": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz",
+      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.9",
+      "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.1.tgz",
+      "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fs": {
+      "version": "0.0.1-security",
+      "resolved": "https://registry.npmmirror.com/fs/-/fs-0.0.1-security.tgz",
+      "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "optional": true
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
+      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+      "optional": true,
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/image-size": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz",
+      "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
+      "optional": true,
+      "bin": {
+        "image-size": "bin/image-size.js"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-what": {
+      "version": "3.14.1",
+      "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz",
+      "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA=="
+    },
+    "node_modules/less": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmmirror.com/less/-/less-4.2.1.tgz",
+      "integrity": "sha512-CasaJidTIhWmjcqv0Uj5vccMI7pJgfD9lMkKtlnTHAdJdYK/7l8pM9tumLyJ0zhbD4KJLo/YvTj+xznQd5NBhg==",
+      "dependencies": {
+        "copy-anything": "^2.0.1",
+        "parse-node-version": "^1.0.1",
+        "tslib": "^2.3.0"
+      },
+      "bin": {
+        "lessc": "bin/lessc"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "optionalDependencies": {
+        "errno": "^0.1.1",
+        "graceful-fs": "^4.1.2",
+        "image-size": "~0.5.0",
+        "make-dir": "^2.1.0",
+        "mime": "^1.4.1",
+        "needle": "^3.1.0",
+        "source-map": "~0.6.0"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.15",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.15.tgz",
+      "integrity": "sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw==",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0"
+      }
+    },
+    "node_modules/make-dir": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz",
+      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+      "optional": true,
+      "dependencies": {
+        "pify": "^4.0.1",
+        "semver": "^5.6.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "optional": true,
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mutation-observer": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/mutation-observer/-/mutation-observer-1.0.3.tgz",
+      "integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA=="
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.8",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.8.tgz",
+      "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/needle": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz",
+      "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
+      "optional": true,
+      "dependencies": {
+        "iconv-lite": "^0.6.3",
+        "sax": "^1.2.4"
+      },
+      "bin": {
+        "needle": "bin/needle"
+      },
+      "engines": {
+        "node": ">= 4.4.x"
+      }
+    },
+    "node_modules/p-queue": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmmirror.com/p-queue/-/p-queue-8.0.1.tgz",
+      "integrity": "sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==",
+      "dependencies": {
+        "eventemitter3": "^5.0.1",
+        "p-timeout": "^6.1.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-timeout": {
+      "version": "6.1.3",
+      "resolved": "https://registry.npmmirror.com/p-timeout/-/p-timeout-6.1.3.tgz",
+      "integrity": "sha512-UJUyfKbwvr/uZSV6btANfb+0t/mOhKV/KXcCUTp8FcQI+v/0d+wXqH4htrW0E4rR6WiEO/EPvUFiV9D5OI4vlw==",
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/parse-node-version": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz",
+      "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+    },
+    "node_modules/pify": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz",
+      "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+      "optional": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.4.49",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz",
+      "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.7",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
+    "node_modules/prr": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz",
+      "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
+      "optional": true
+    },
+    "node_modules/recorder-core": {
+      "version": "1.3.24102001",
+      "resolved": "https://registry.npmmirror.com/recorder-core/-/recorder-core-1.3.24102001.tgz",
+      "integrity": "sha512-ZH1LX6aPmmg0LV49Z74Lb4itGBvWmVKpEN4+PjXYls8nSECHdXO23Hq3lR01Pca7cdNMDkTAkLGpsvAMnwA/LA=="
+    },
+    "node_modules/regenerator-runtime": {
+      "version": "0.14.1",
+      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+    },
+    "node_modules/rollup": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.28.1.tgz",
+      "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "1.0.6"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.28.1",
+        "@rollup/rollup-android-arm64": "4.28.1",
+        "@rollup/rollup-darwin-arm64": "4.28.1",
+        "@rollup/rollup-darwin-x64": "4.28.1",
+        "@rollup/rollup-freebsd-arm64": "4.28.1",
+        "@rollup/rollup-freebsd-x64": "4.28.1",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.28.1",
+        "@rollup/rollup-linux-arm-musleabihf": "4.28.1",
+        "@rollup/rollup-linux-arm64-gnu": "4.28.1",
+        "@rollup/rollup-linux-arm64-musl": "4.28.1",
+        "@rollup/rollup-linux-loongarch64-gnu": "4.28.1",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1",
+        "@rollup/rollup-linux-riscv64-gnu": "4.28.1",
+        "@rollup/rollup-linux-s390x-gnu": "4.28.1",
+        "@rollup/rollup-linux-x64-gnu": "4.28.1",
+        "@rollup/rollup-linux-x64-musl": "4.28.1",
+        "@rollup/rollup-win32-arm64-msvc": "4.28.1",
+        "@rollup/rollup-win32-ia32-msvc": "4.28.1",
+        "@rollup/rollup-win32-x64-msvc": "4.28.1",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "optional": true
+    },
+    "node_modules/sax": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz",
+      "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
+      "optional": true
+    },
+    "node_modules/semver": {
+      "version": "5.7.2",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz",
+      "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+      "optional": true,
+      "bin": {
+        "semver": "bin/semver"
+      }
+    },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "optional": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+    },
+    "node_modules/undici-types": {
+      "version": "6.20.0",
+      "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.20.0.tgz",
+      "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+      "dev": true,
+      "optional": true,
+      "peer": true
+    },
+    "node_modules/vant": {
+      "version": "4.9.10",
+      "resolved": "https://registry.npmmirror.com/vant/-/vant-4.9.10.tgz",
+      "integrity": "sha512-N+QwOuhDxrH2f6+kN05ot6DHBvaM0e1lcoXVvf12rad2KnlybPQ9gjm0d+R+Nz/zydZbe3Fz6bwTssHItri0sw==",
+      "dependencies": {
+        "@vant/popperjs": "^1.3.0",
+        "@vant/use": "^1.6.0",
+        "@vue/shared": "^3.5.13"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
+    },
+    "node_modules/vconsole": {
+      "version": "3.15.1",
+      "resolved": "https://registry.npmmirror.com/vconsole/-/vconsole-3.15.1.tgz",
+      "integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==",
+      "dependencies": {
+        "@babel/runtime": "^7.17.2",
+        "copy-text-to-clipboard": "^3.0.1",
+        "core-js": "^3.11.0",
+        "mutation-observer": "^1.0.3"
+      }
+    },
+    "node_modules/vite": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmmirror.com/vite/-/vite-6.0.3.tgz",
+      "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==",
+      "dev": true,
+      "dependencies": {
+        "esbuild": "^0.24.0",
+        "postcss": "^8.4.49",
+        "rollup": "^4.23.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+        "jiti": ">=1.21.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.16.0",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "jiti": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz",
+      "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.13",
+        "@vue/compiler-sfc": "3.5.13",
+        "@vue/runtime-dom": "3.5.13",
+        "@vue/server-renderer": "3.5.13",
+        "@vue/shared": "3.5.13"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.0.13",
+      "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.0.13.tgz",
+      "integrity": "sha512-LmXrC+BkDRLak+d5xTMgUYraT3Nj0H/vCbP+7usGvIl9Viqd1UP6AsP0i69pSbn9O0dXK/xCdp4yPw21HqV9Jw==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/zrender": {
+      "version": "5.6.1",
+      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
+      "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    }
+  }
+}

+ 31 - 0
package.json

@@ -0,0 +1,31 @@
+{
+  "name": "video-test",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@microsoft/fetch-event-source": "^2.0.1",
+    "axios": "^1.7.9",
+    "delay": "^6.0.0",
+    "echarts": "^5.5.1",
+    "fs": "^0.0.1-security",
+    "less": "^4.2.1",
+    "p-queue": "^8.0.1",
+    "recorder-core": "^1.3.24102001",
+    "vant": "^4.9.10",
+    "vconsole": "^3.15.1",
+    "vue": "^3.5.13",
+    "vue-router": "^4.0.13"
+  },
+  "devDependencies": {
+    "@vicons/material": "^0.13.0",
+    "@vicons/utils": "^0.1.4",
+    "@vitejs/plugin-vue": "^5.2.1",
+    "vite": "^6.0.1"
+  }
+}

BIN
public/image0.jpg


BIN
public/image0.webp


BIN
public/image1.webp


BIN
public/image2.webp


+ 1 - 0
public/vite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 9 - 0
src/App.vue

@@ -0,0 +1,9 @@
+<template>
+  <router-view></router-view>
+</template>
+<style>
+p{
+  margin: 0;
+  padding: 0;
+}
+</style>

BIN
src/assets/WechatIMG221.jpg


BIN
src/assets/WechatIMG222.jpg


BIN
src/assets/banner1.jpg


BIN
src/assets/banner2.jpg


BIN
src/assets/banner3.jpg


BIN
src/assets/banner4.jpg


BIN
src/assets/head.jpg


BIN
src/assets/robot.jpg


BIN
src/assets/role.jpg


BIN
src/assets/role1.jpg


BIN
src/assets/role2.jpg


BIN
src/assets/role3.jpg


+ 1 - 0
src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 43 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,43 @@
+<script setup>
+import { ref } from 'vue'
+
+defineProps({
+  msg: String,
+})
+
+const count = ref(0)
+</script>
+
+<template>
+  <h1>{{ msg }}</h1>
+
+  <div class="card">
+    <button type="button" @click="count++">count is {{ count }}</button>
+    <p>
+      Edit
+      <code>components/HelloWorld.vue</code> to test HMR
+    </p>
+  </div>
+
+  <p>
+    Check out
+    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
+      >create-vue</a
+    >, the official Vue + Vite starter
+  </p>
+  <p>
+    Learn more about IDE Support for Vue in the
+    <a
+      href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
+      target="_blank"
+      >Vue Docs Scaling up Guide</a
+    >.
+  </p>
+  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
+</template>
+
+<style scoped>
+.read-the-docs {
+  color: #888;
+}
+</style>

+ 51 - 0
src/components/NavBarPage.vue

@@ -0,0 +1,51 @@
+<script setup>
+import { ref, reactive, toRefs, onMounted} from 'vue'
+const onClickLeft = () => {
+  if(props.type=='other'){
+    history.back();
+  }else{
+    window.location.href = '/';
+  }
+  
+}
+const props = defineProps({
+  title: {
+    type: String,
+    default: '标题'
+  },
+  type:{
+    type: String,
+    default:'other'
+  }
+})
+</script>
+<template>
+  <div>
+    <van-nav-bar :title="title" left-arrow @click-left="onClickLeft" class="custom-navbar" />
+  </div>
+</template>
+
+<style scoped>
+
+/* 自定义导航栏样式 */
+.custom-navbar {
+    background-color: #fff;
+    color: black;
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    z-index: 999;
+}
+
+/* 更改左侧箭头颜色 */
+.custom-navbar :deep(.van-nav-bar__arrow) {
+    color: black;
+}
+
+.custom-navbar::before,
+.custom-navbar::after {
+    display: none !important;
+    /* 直接隐藏伪元素 */
+}
+</style>

+ 36 - 0
src/hooks/useScroll.js

@@ -0,0 +1,36 @@
+import { nextTick } from 'vue'
+
+function useScroll() {
+  let scrollRef = null
+
+  const scrollToBottom = async () => {
+    await nextTick()
+    if (scrollRef)
+      scrollRef.scrollTop = scrollRef.scrollHeight
+  }
+
+  const scrollToTop = async () => {
+    await nextTick()
+    if (scrollRef)
+      scrollRef.scrollTop = 0
+  }
+
+  const scrollToBottomIfAtBottom = async () => {
+    await nextTick()
+    if (scrollRef) {
+      const threshold = 100 // 阈值,表示滚动条到底部的距离阈值
+      const distanceToBottom = scrollRef.scrollHeight - scrollRef.scrollTop - scrollRef.clientHeight
+      if (distanceToBottom <= threshold)
+        scrollRef.scrollTop = scrollRef.scrollHeight
+    }
+  }
+
+  return {
+    scrollRef,
+    scrollToBottom,
+    scrollToTop,
+    scrollToBottomIfAtBottom,
+  }
+}
+
+export default useScroll

+ 20 - 0
src/main.js

@@ -0,0 +1,20 @@
+import { createApp } from 'vue'
+import './style.css'
+import App from './App.vue'
+import router from './router';
+import Vant from 'vant';
+import VConsole from 'vconsole'
+// 2. 引入组件样式
+import 'vant/lib/index.css';
+
+const app = createApp(App);
+
+// 初始化 VConsole
+const vConsole = new VConsole()
+
+// 将封装后的 Vue Router 注入应用
+app.use(router);
+
+app.use(Vant);
+
+app.mount('#app');

+ 66 - 0
src/router/index.js

@@ -0,0 +1,66 @@
+import { createRouter, createWebHashHistory } from 'vue-router';
+
+const routes = [
+  // 定义你的路由规则
+  {
+    path: '/',
+    redirect: '/home',
+  },
+  {
+    path: '/demo',
+    name: 'DemoHome',
+    component: () => import('../views/demo/Home.vue'),
+  },
+  {
+    path: '/home',
+    name: 'home',
+    component: () => import('../views/HomeView.vue'),
+  },
+  {
+    path:'/detail',
+    name:'detail',
+    component:()=>import('../views/DetailView.vue')
+  },
+  {
+    path:'/result',
+    name:'result',
+    component:()=>import('../views/ResultView.vue')
+  },
+  {
+    path: '/demo/pic',
+    name: 'DemoPic',
+    component: () => import('../views/demo/Pic.vue'),
+  },
+  {
+    path: '/demo/asr',
+    name: 'DemoAsr',
+    component: () => import('../views/demo/Asr.vue'),
+  },
+  {
+    path: '/demo/asrDemo',
+    name: 'DemoAsrDemo',
+    component: () => import('../views/demo/AsrDemo.vue'),
+  },
+  {
+    path: '/demo/tts',
+    name: 'DemoTts',
+    component: () => import('../views/demo/Tts.vue'),
+  },
+  {
+    path: '/demo/steam',
+    name: 'DemoSteam',
+    component: () => import('../views/demo/Steam.vue'),
+  },
+  {
+    path: '/chatTts',
+    name: 'ChatTts',
+    component: () => import('../views/ChatTts.vue'),
+  }
+];
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes,
+});
+
+export default router;

+ 0 - 0
src/style.css


+ 458 - 0
src/views/ChatTts.vue

@@ -0,0 +1,458 @@
+<script setup>
+import { ref, computed, onMounted, onBeforeUnmount } from "vue";
+import BG from "./components/BG.vue";
+import BottomArea from "./components/BottomArea.vue";
+import ChatList from "./components/ChatList.vue";
+import NavBarPage from "../components/NavBarPage.vue";
+import PQueue from "p-queue";
+import axios from "axios";
+import { fetchEventSource } from "@microsoft/fetch-event-source";
+import { useRoute, useRouter } from "vue-router";
+import delay from "delay";
+
+const route = useRoute();
+const router = useRouter();
+const conversationId = computed(() => route.query.taskId); // 会话ID
+const round = computed(() => route.query.round); // 轮次
+const sessionId = ref("");
+
+const currentRate = ref(0);
+const currentRound = computed(() => {
+  const filteredMessages = chatList.value.filter(
+    (message) =>
+      message.text !== "我没有听清,麻烦在重复一下。" && message.text !== ""
+  );
+  return Math.floor(filteredMessages.length / 2);
+});
+// 当前进度百分比整数部分
+const rate = computed(() => {
+  return Math.floor((currentRound.value / round.value) * 100);
+});
+
+const text = computed(() => `${currentRound.value} / ${round.value}`);
+
+// 对话记录
+const chatList = ref([]);
+let i = 0;
+// 我 我是 我是中国人 我是中国人的英雄 我是中国人的英雄。[
+const updateChatList = async (message = "", loading = true) => {
+  const timer = setInterval(() => {
+    i = i + 1;
+    chatList.value = chatList.value.map((item, index) => {
+      if (index == chatList.value.length - 1) {
+        return {
+          ...item,
+          text: message.substring(0, i),
+        };
+      } else {
+        return item;
+      }
+    });
+    localStorage.setItem("chatHistory", JSON.stringify(chatList.value));
+    if (i >= message.length) {
+      clearInterval(timer);
+    }
+  }, 50);
+
+  // chatList.value = chatList.value.map((item) =>
+  //   item.loading
+  //     ? message
+  //       ? { ...item, text: message, loading }
+  //       : { ...item, loading }
+  //     : item
+  // );
+  // localStorage.setItem("chatHistory", JSON.stringify(chatList.value));
+};
+
+// 创建一个队列实例,设置并发数为 1
+const queue = new PQueue({ concurrency: 1 });
+const queue1 = new PQueue({ concurrency: 1 });
+const queue2 = new PQueue({ concurrency: 1 });
+
+// 需要tts的文本队列
+const messageQueue = [];
+
+// lastStrIndex 用于记录上一个字符串的结束位置
+let lastStrIndex = 0;
+
+// 将字符串根据标点符号断句分割,并添加到messageQueue中
+const splitMessage = async (str) => {
+  const punctuation = ["。", "!", "?", ";", ":"];
+
+  for (let i = lastStrIndex; i < str.length; i++) {
+    if (punctuation.includes(str[i])) {
+      const message = str.slice(lastStrIndex, i + 1);
+      console.log(message, "==========");
+      await playTTS(message);
+      lastStrIndex = i + 1; // 更新上一个字符串的结束位置
+    }
+  }
+};
+
+const handleStartRecord = async () => {
+  queue.clear();
+  queue1.clear();
+};
+
+const generateSessionId = () => {
+  // 获取当前时间戳
+  const timestamp = Date.now().toString();
+  // 生成一个随机数
+  const randomNum = Math.floor(Math.random() * 10000);
+  // 将时间戳和随机数拼接成会话ID
+  const sessionId = `${timestamp}${randomNum}`;
+  return sessionId;
+};
+
+// 发送消息
+const handleStopRecord = (message) => {
+  lastStrIndex = 0; // 重置
+  chatList.value = [
+    ...chatList.value,
+    {
+      id: Date.now(),
+      type: "user",
+      text: message,
+      loading: false,
+    },
+    {
+      id: Date.now(),
+      type: "gpt",
+      text: "",
+      loading: true,
+    },
+  ];
+  sessionId.value = generateSessionId();
+  chatGpt(message);
+};
+
+const playTTS1 = async (ttsMessage) => {
+  try {
+    const res = await axios.post(
+      `/openapi-stg/ai/voice/tts/v2`,
+      { sessionId: "N7FB_G0WlrOLjc", text: ttsMessage },
+      {
+        responseType: "arraybuffer",
+        headers: {
+          "X-Ai-TTS-Appid": "2b1317fb5b284b308dc90a6fdeae6c4e",
+        },
+      }
+    );
+    console.log(res.data);
+
+    queue1.add(async () => {
+      // 播放获取到的音频
+      await playAudio(res.data);
+    });
+  } catch (error) {
+    console.error("Error calling TTS API:", error);
+  }
+};
+
+const playTTS = async (ttsMessage) => {
+  try {
+    const res = await axios.get(
+      `/openapi-prd/ai/ttt/synthesize?text=${ttsMessage}&sessionId=${sessionId.value}`,
+      {
+        responseType: "arraybuffer",
+      }
+    );
+    console.log(res.data);
+
+    queue1.add(async () => {
+      // 播放获取到的音频
+      await playAudio(res.data);
+    });
+  } catch (error) {
+    console.error("Error calling TTS API:", error);
+  }
+};
+
+const playAudio1 = (audioData, options = {}) => {
+  return new Promise((resolve, reject) => {
+    const blob = new Blob([audioData], { type: "audio/wav" });
+    const url = URL.createObjectURL(blob);
+    const audio = new Audio(url);
+
+    audio.setAttribute("id", "audio");
+    audio.setAttribute("autoplay", "autoplay");
+
+    if (options.volume) {
+      audio.volume = options.volume; // 设置音量
+    }
+
+    audio.onended = () => {
+      URL.revokeObjectURL(url);
+      resolve();
+    };
+
+    audio.onerror = (error) => {
+      URL.revokeObjectURL(url);
+      reject(error);
+    };
+
+    audio
+      .play()
+      .then(() => console.log("Audio playing"))
+      .catch(reject);
+  });
+};
+
+const playAudio = (audioData, options = {}) => {
+  return new Promise((resolve, reject) => {
+    const audioContext = new (window.AudioContext ||
+      window.webkitAudioContext)();
+    const blob = new Blob([audioData], { type: "audio/wav" });
+    const url = URL.createObjectURL(blob);
+    const audioBufferSourceNode = audioContext.createBufferSource();
+
+    fetch(url)
+      .then((response) => response.arrayBuffer())
+      .then((arrayBuffer) => audioContext.decodeAudioData(arrayBuffer))
+      .then((audioBuffer) => {
+        audioBufferSourceNode.buffer = audioBuffer;
+        audioBufferSourceNode.connect(audioContext.destination);
+
+        if (options.volume) {
+          audioBufferSourceNode.gain.value = options.volume; // 设置音量
+        }
+
+        audioBufferSourceNode.start(0);
+
+        audioBufferSourceNode.onended = () => {
+          URL.revokeObjectURL(url);
+          resolve();
+        };
+
+        audioBufferSourceNode.onerror = (error) => {
+          URL.revokeObjectURL(url);
+          reject(error);
+        };
+      })
+      .catch(reject);
+  });
+};
+
+const chatGpt = async (userMessage) => {
+  const downloadUrl = "/openapi-prd/ai/intelligent-tutoring/task/dialogue";
+  i = 0;
+  queue2.add(() => delay(3000));
+  try {
+    const response = await axios.post(
+      downloadUrl,
+      {
+        conversationId: conversationId.value,
+        content: userMessage,
+      },
+      {
+        headers: {
+          "Content-Type": "application/json",
+        },
+        onDownloadProgress: ({ event }) => {
+          const xhr = event.target;
+          let { responseText } = xhr;
+          console.log("responseText", responseText);
+          if (responseText.includes("code") && responseText.includes("400")) {
+            // console.log("responseTextcode", responseText);
+            // 更新聊天列表
+            const text = "我没有听清,麻烦在重复一下。";
+            updateChatList(text, false);
+            queue.add(async () => {
+              await splitMessage(text);
+            });
+          } else {
+            // 用于语音播报
+            queue.add(async () => {
+              await splitMessage(responseText);
+            });
+            // const timer = setInterval(() => {
+            //   i = i + 1;
+            //   //updateChatList(responseText.substring(0, i));
+            //   queue2.add(
+            //     async () => await updateChatList(responseText.substring(0, i))
+            //   );
+            //   if (i >= responseText.length) {
+            //     clearInterval(timer);
+            //   }
+            //   console.log(
+            //     i,
+            //     responseText.substring(i),
+            //     "=======responseText.substring(i)"
+            //   );
+            //   if (
+            //     responseText.substring(i).indexOf("[DONE]") === 0 ||
+            //     responseText.substring(i).indexOf("[DONE]") === 1
+            //   ) {
+            //     console.log("========done===========");
+            //     updateChatList("", false);
+            //     // 保存聊天记录到本地
+            //     const chatHistory = JSON.stringify(chatList.value);
+            //     localStorage.setItem("chatHistory", chatHistory);
+            //   }
+            //}, 200);
+
+            queue2.add(async () => await updateChatList(responseText));
+            //updateChatList(responseText);
+          }
+        },
+      }
+    );
+    //queue2.add(async () => await updateChatList("", false));
+  } catch (error) {
+    //updateChatList("", false);
+    console.error("出错:", error);
+  }
+};
+
+const chatGpt2 = (userMessage) => {
+  const params = {
+    conversationId: "2c64eb8b69be432ca0bb9ae55bc78def",
+    content: userMessage,
+  };
+  const ctrlAbout = new AbortController();
+  fetchEventSource(
+    "https://fls-ai.pingan.com.cn/openapi/ai/intelligent-tutoring/task/dialogue",
+    {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json", // 文本返回格式
+      },
+      body: JSON.stringify(params),
+      signal: ctrlAbout.signal,
+      openWhenHidden: true, // 在浏览器标签页隐藏时保持与服务器的EventSource连接
+      onmessage(res) {
+        // 操作流式数据
+        console.log(JSON.parse(res.data), "==========");
+      },
+      onclose(res) {
+        // 关闭流
+      },
+      onerror(error) {
+        // 返回流报错
+      },
+    }
+  );
+};
+
+const chatGpt1 = async (userMessage) => {
+  const downloadUrl =
+    "https://fls-ai.pingan.com.cn/openapi/ai/llm/forward/api/ai/nlp/dialogue";
+
+  try {
+    const response = await axios.post(
+      downloadUrl,
+      {
+        conversationId: "1976cfe3a5174f9ba768677f789cad7e",
+        content: `${userMessage},请输出纯文本的回答,不要使用markdown输出`,
+        messageId: Date.now(),
+        applicationId: "",
+      },
+      {
+        headers: {
+          "Team-Id": "123456",
+          Authorization: "9b2f86c99b5847739045e6b85f355301",
+          "Content-Type": "application/json",
+        },
+        onDownloadProgress: ({ event }) => {
+          const xhr = event.target;
+          const { responseText } = xhr;
+          console.log(responseText);
+          // 更新聊天列表
+          updateChatList(responseText);
+          // 用于语音播报
+          queue.add(async () => {
+            await splitMessage(responseText);
+          });
+        },
+      }
+    );
+    updateChatList("", false);
+  } catch (error) {
+    updateChatList("", false);
+    console.error("下载文件时出错:", error);
+  }
+};
+const next = () => {
+  router.push({
+    name: "result",
+    query: { conversationId: conversationId.value },
+  });
+};
+
+const loadChatHistory = () => {
+  const chatHistory = localStorage.getItem("chatHistory");
+  if (chatHistory) {
+    const history = JSON.parse(chatHistory);
+    history.forEach((item) => {
+      chatList.value.push(item);
+    });
+  }
+};
+
+// 在组件挂载时调用
+onMounted(() => {
+  loadChatHistory();
+});
+
+// onBeforeUnmount(() => {
+//   localStorage.removeItem("chatHistory");
+// });
+</script>
+
+<template>
+  <div class="main">
+    <nav-bar-page title="考试" />
+    <div class="rate-circle">
+      <van-circle
+        :stroke-width="80"
+        size="70px"
+        v-model:current-rate="currentRate"
+        :rate="rate"
+        :text="text"
+      />
+    </div>
+    <BG />
+    <ChatList :chatList="chatList" />
+    <BottomArea
+      @startRecord="handleStartRecord"
+      @stopRecord="handleStopRecord"
+      v-show="rate < 100"
+    />
+    <div v-show="rate >= 100" class="next-btn">
+      <van-button style="width: 100%" @click="next" type="primary"
+        >已完成对话,下一步</van-button
+      >
+    </div>
+  </div>
+</template>
+<style>
+* {
+  -webkit-touch-callout: none; /*系统默认菜单被禁用*/
+  -webkit-user-select: none; /*webkit浏览器*/
+  -khtml-user-select: none; /*早期浏览器*/
+  -moz-user-select: none; /*火狐*/
+  -ms-user-select: none; /*IE10*/
+  user-select: none;
+}
+</style>
+
+<style scoped>
+.main {
+  width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+}
+.rate-circle {
+  position: fixed;
+  right: 20px;
+  top: 70px;
+  z-index: 9999;
+}
+.next-btn {
+  position: fixed;
+  bottom: calc(20px + env(safe-area-inset-bottom));
+  width: 100%;
+  box-sizing: border-box;
+  padding: 0 20px;
+}
+</style>

+ 287 - 0
src/views/DetailView.vue

@@ -0,0 +1,287 @@
+<script setup>
+import { ref, reactive, toRefs, onMounted } from "vue";
+import NavBarPage from "../components/NavBarPage.vue";
+import selectPage from "./result/selectPage.vue";
+import { fetchTaskCreate } from "./utils/api";
+import { showToast } from "vant";
+import { useRouter } from "vue-router";
+import { RollerSkatingRound } from "@vicons/material";
+
+const router = useRouter();
+
+const selectedRole = ref("");
+const selectedNature = ref(null);
+const selectedRounds = ref(0);
+
+const rolelist = ref([]);
+// const roleOptions = [
+//   { text: "投资人", value: "投资人" },
+//   { text: "店总", value: "店总" },
+//   { text: "销售经理", value: "销售经理" },
+//   { text: "金融经理", value: "金融经理" },
+//   { text: "销售顾问", value: "销售顾问" },
+// ];
+const roleOptions = JSON.parse(localStorage.getItem('role')).roleOption
+
+const roundsOptions = [
+  { text: 5, value: 5 },
+  { text: 8, value: 8 },
+  { text: 10, value: 10 },
+];
+
+const naturesOptions = [
+  { text: "固执己见,不愿妥协", value: "固执己见,不愿妥协" },
+  { text: "细心周到,考虑周全", value: "细心周到,考虑周全" },
+  { text: "易怒暴躁,难以沟通", value: "易怒暴躁,难以沟通" },
+  { text: "幽默风趣,气氛活跃", value: "幽默风趣,气氛活跃" },
+  { text: "自私冷漠,缺乏同理", value: "自私冷漠,缺乏同理" },
+];
+
+const handleSelectNature = (option) => {
+  selectedNature.value = option.value;
+};
+
+const handleSelectRoles = (option) => {
+  selectedRole.value = option.value;
+};
+
+const handleSelectRounds = (option) => {
+  selectedRounds.value = option.value;
+};
+
+const handleExam = async () => {
+  if (!selectedRole.value || !selectedNature.value || !selectedRounds.value) {
+    showToast("请选择角色、性格和轮数");
+    return;
+  }
+  const data = {
+    tutoringRole: selectedRole.value,
+    personality: selectedNature.value,
+    round: selectedRounds.value,
+  };
+  try {
+    const { body } = await fetchTaskCreate(data);
+    router.push({
+      name: "ChatTts",
+      query: { taskId: body, round: selectedRounds.value },
+    });
+  } catch (error) {
+    console.log(error);
+  }
+};
+onMounted(() => {
+  const role = JSON.parse(localStorage.getItem('role'))
+  rolelist.value = role
+  localStorage.removeItem("chatHistory");
+});
+</script>
+<template>
+  <div class="detail">
+    <nav-bar-page title="课程详情" />
+    <div style="height: 46px"></div>
+    <div class="content">
+      <div class="banner">
+        <img :src="rolelist.image" alt="" />
+      </div>
+      <div class="produce">
+        <p>{{ rolelist.title }}</p>
+        <div class="p_content">
+          <span class="bg">背景:</span>
+          <span>{{ rolelist.description }}</span>
+        </div>
+      </div>
+      <div class="rules">
+        <div class="title">
+          <van-icon
+            name="records-o"
+            size="1.5rem"
+            color="rgba(64,149,229,1)"
+            style="font-weight: bold"
+          />
+          <span>规则</span>
+        </div>
+        <div class="r_content">
+          <div class="r_item">
+            <label for="roles">陪练角色</label>
+            <selectPage
+              :optionValue="roleOptions"
+              placeholder="请选择角色"
+              @update:selectedOption="handleSelectRoles"
+            />
+          </div>
+          <div class="r_item">
+            <label for="nature">陪练性格</label>
+            <selectPage
+              :optionValue="naturesOptions"
+              placeholder="请选择性格"
+              @update:selectedOption="handleSelectNature"
+            />
+          </div>
+          <div class="r_item">
+            <label for="rounds">交互轮次</label>
+            <selectPage
+              :optionValue="roundsOptions"
+              placeholder="请选择轮次"
+              @update:selectedOption="handleSelectRounds"
+            />
+          </div>
+        </div>
+      </div>
+      <div class="bottom">
+        <button class="exam" @click="handleExam">考试</button>
+        <button>练习</button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="less">
+.detail {
+  width: 100%;
+  height: 100%;
+  // overflow: hidden;
+
+  /* background-color: #f5f5f5; */
+  .content {
+    padding: 0 1.88rem;
+  }
+
+  .produce {
+    margin-top: 1.63rem;
+
+    p {
+      color: rgba(16, 16, 16, 1);
+      font-size: 1rem;
+      font-family: Times New Roman-regular;
+    }
+
+    .p_content {
+      margin-top: 0.5rem;
+      width: 100%;
+      max-height: 8rem;
+      overflow: scroll;
+    }
+
+    span {
+      line-height: 1.25rem;
+      color: rgba(102, 102, 102, 1);
+      font-size: 0.88rem;
+      text-align: left;
+      font-family: SourceHanSansSC-bold;
+    }
+
+    .bg {
+      font-weight: 600;
+      color: #000;
+    }
+  }
+
+  .rules {
+    margin: 2.38rem 0;
+    width: 100%;
+    height: 13.69rem;
+    line-height: 1.25rem;
+    border-radius: 0.75rem;
+    background-color: rgba(255, 255, 255, 1);
+    color: rgba(16, 16, 16, 1);
+    font-size: 0.88rem;
+    text-align: center;
+    box-shadow: 0rem 0.06rem 0.88rem 0rem rgba(0, 1, 130, 0.08);
+    font-family: -regular;
+
+    .title {
+      display: flex;
+      align-items: center;
+      padding: 1.03rem 0.88rem;
+    }
+
+    .title span {
+      margin-left: 0.31rem;
+      color: rgba(64, 149, 229, 1);
+      font-size: 1rem;
+      font-family: Times New Roman-regular;
+    }
+
+    .r_content {
+      margin-top: 0.44rem;
+    }
+
+    .r_item {
+      display: flex;
+      // justify-content: space-between;
+      align-items: center;
+      padding: 0 1.75rem;
+      margin-bottom: 0.75rem;
+      font-size: 0.88rem;
+      color: rgba(16, 16, 16, 1);
+    }
+
+    .r_item select {
+      width: 11.56rem;
+      height: 2rem;
+      line-height: 1.25rem;
+      border-radius: 0.38rem;
+      font-family: PingFangSC-regular;
+      border: 0.06rem solid rgba(187, 187, 187, 1);
+      padding: 0 10px;
+      -webkit-appearance: none;
+      -moz-appearance: none;
+      appearance: none;
+      background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20fill%3D%22%23000%22%20fill-opacity%3D%22.9%22%20fill-rule%3D%22evenodd%22%20d%3D%22M17.35%209.65a.5.5%200%200%200-.7%200L12%2014.29%207.35%209.65a.5.5%200%201%200-.7.7l5%205c.2.2.5.2.7%200l5-5a.5.5%200%200%200%200-.7%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")
+        no-repeat right 10px center;
+      background-size: 1.5rem 1.5rem;
+      /* 调整箭头的大小 */
+    }
+
+    .r_item label {
+      // width: 3.5rem;
+      // height: 1.25rem;
+      // line-height: 1.25rem;
+      // color: rgba(16,16,16,1);
+      // font-size: 0.88rem;
+      // text-align: left;
+      font-family: PingFangSC-medium;
+      margin-right: 1.1rem;
+    }
+  }
+
+  .bottom {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 1.38rem;
+
+    button {
+      width: 10rem;
+      height: 2.44rem;
+      line-height: 1.25rem;
+      border-radius: 0.38rem 0.38rem 0.38rem 0.38rem;
+      background-color: rgba(243, 249, 255, 1);
+      font-size: 0.88rem;
+      text-align: center;
+      font-family: -regular;
+      border: 0.06rem solid rgba(163, 208, 253, 1);
+      color: #1989fa;
+    }
+
+    .exam {
+      color: #fff;
+      background-color: rgba(64, 149, 229, 1);
+      margin-right: 0.63rem;
+    }
+  }
+}
+
+.banner {
+  height: 11.63rem;
+  border-radius: 0.75rem;
+  background-color: rgba(229, 229, 229, 1);
+  overflow: hidden;
+}
+
+.banner img {
+  width: 100%;
+  height: 11.63rem;
+  object-fit: cover;
+}
+</style>

+ 305 - 0
src/views/HomeView.vue

@@ -0,0 +1,305 @@
+<script setup>
+import { ref, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import banner1 from "../assets/banner1.jpg";
+import banner2 from "../assets/banner2.jpg";
+import banner3 from "../assets/banner3.jpg";
+import banner4 from "../assets/banner4.jpg";
+import role1 from "../assets/role1.jpg";
+import role2 from "../assets/role2.jpg";
+import role3 from "../assets/role3.jpg";
+import role from "../assets/role.jpg";
+
+const images = ref([
+  { url: banner1 },
+  { url: banner2 },
+  { url: banner3 },
+  { url: banner4 },
+]);
+
+const router = useRouter();
+
+const active = ref(0);
+const value = ref("");
+
+const cardlist = ref([
+  {
+    title: "新客触达",
+    image: role3,
+    subTitle: "白名单新客触达",
+    description: "通过电话联系同业存客、或白户客户,摸排客户融资需求,寻找商机",
+    roleOption:[
+      {text: '同业存客', value: '同业存客'},
+      {text: '白户客户', value: '白户客户'}
+    ]
+  },
+  {
+    title: "转介营销",
+    image: role2,
+    subTitle: "转介名单拓展营销",
+    description:'通过电话联系渠道(或存量客户)转介名单,开展营销,寻找商机',
+    roleOption:[
+      {text: '渠道转介', value: '渠道转介'},
+      {text: '存量客户', value: '存量客户'},
+    ]
+  },
+  {
+    title: "存量客户",
+    image: role1,
+    subTitle: "存量客户名单",
+    description: "通过电话联系已有的存量客户名单,开展营销,寻找商机",
+    roleOption:[
+      {text: '存量客户', value: '存量客户'},
+    ]
+  },
+  {
+    title: "区域陌拜",
+    image: role,
+    subTitle: "地推周边企业陌拜",
+    description:"通过地推的方式搜索新客户,并进行现场陌拜,建立关系,挖掘商机",
+    roleOption:[
+      {text: '周边企业', value: '周边企业'},
+    ]
+  }
+])
+
+const handleDetail = (item) => {
+  localStorage.setItem("role", JSON.stringify(item));
+  router.push("/detail");
+};
+onMounted(() => {
+  localStorage.removeItem("chatHistory");
+});
+</script>
+<template>
+  <div class="home">
+    <!-- 人物头像 -->
+    <div class="avatar">
+      <header>
+        <img src="../assets/head.jpg" alt="" />
+        <div class="head_content">
+          <span>Chris B</span>
+          <span>探索未知,发现自我~</span>
+        </div>
+      </header>
+      <div class="search">
+        <van-search v-model="value" placeholder="搜索" />
+      </div>
+      <div class="banner">
+        <van-swipe :autoplay="3000">
+          <van-swipe-item v-for="(image, index) in images" :key="index">
+            <div class="image-wrapper">
+              <img :src="image.url" alt="轮播图片" />
+              <div class="text-wrapper">
+                <div>模拟对话场景,提升业务能力</div>
+                <button>查看任务</button>
+              </div>
+            </div>
+          </van-swipe-item>
+        </van-swipe>
+      </div>
+      <div class="card_list">
+        <p>陪练课程</p>
+        <!-- <div class="card_content">
+          <div class="card_item" @click="handleDetail()">
+            <img src="../assets/role3.jpg" alt="" />
+            <p>新客触达</p>
+            <span>白名单新客触达</span>
+          </div>
+          <div class="card_item">
+            <img src="../assets/role2.jpg" alt="" />
+            <p>转介营销</p>
+            <span>转介名单拓展营销</span>
+          </div>
+          <div class="card_item">
+            <img src="../assets/role1.jpg" alt="" />
+            <p>存量客户</p>
+            <span>存量客户名单</span>
+          </div>
+          <div class="card_item">
+            <img src="../assets/role.jpg" alt="" />
+            <p>区域陌拜</p>
+            <span>地推周边企业陌拜</span>
+          </div>
+        </div> -->
+        <div class="card_content">
+          <div v-for="(item, index) in cardlist" :key="index" class="card_item" @click="handleDetail(item)">
+            <img :src="item.image" alt="" />
+            <p>{{ item.title }}</p>
+            <span>{{ item.subTitle }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 底部tabbar -->
+    <div style="visibility: hidden; height: 50px"></div>
+    <van-tabbar v-model="active">
+      <van-tabbar-item name="home" icon="home-o">首页</van-tabbar-item>
+      <van-tabbar-item name="friends" icon="contact-o">我的</van-tabbar-item>
+    </van-tabbar>
+  </div>
+</template>
+
+<style scoped lang="less">
+p {
+  margin: 0;
+  padding: 0;
+}
+
+html {
+  touch-action: manipulation;
+  /* 禁止双指缩放 */
+}
+
+@media (max-width: 767px) {
+  html {
+    touch-action: pan-y;
+    /* 允许垂直滚动,禁止水平滚动和缩放 */
+  }
+}
+
+.avatar {
+  padding: 0 1.38rem;
+
+  header {
+    padding: 1.31rem 0;
+    display: flex;
+    align-items: center;
+
+    img {
+      width: 3.13rem;
+      height: 3.13rem;
+      border-radius: 6.25rem;
+      background-color: rgba(229, 229, 229, 1);
+      box-shadow: 0rem 0.5rem 1.5rem 0rem rgba(92, 117, 169, 0.4);
+      margin-right: 1.25rem;
+    }
+
+    .head_content {
+      display: flex;
+      flex-direction: column;
+
+      span {
+        line-height: 1.44rem;
+        color: rgba(16, 16, 16, 1);
+        font-size: 1rem;
+        text-align: left;
+        font-family: PingFangSC-medium;
+      }
+
+      span:nth-child(2) {
+        color: rgba(102, 102, 102, 0.7);
+        font-size: 0.88rem;
+        line-height: 1.25rem;
+      }
+    }
+  }
+
+  .search {
+    .van-search {
+      width: 100%;
+      height: 2.5rem;
+      padding: 0;
+    }
+
+    :deep(.van-search__content) {
+      border-radius: 0.5rem;
+    }
+  }
+
+  .banner {
+    margin: 1.25rem 0 1.25rem 0;
+  }
+
+  .image-wrapper {
+    position: relative;
+    width: 100%;
+    height: 8.75rem;
+    border-radius: 0.75rem;
+    overflow: hidden;
+
+    img {
+      width: 100%;
+      height: auto;
+      object-fit: cover;
+    }
+
+    .text-wrapper {
+      position: absolute;
+      left: 0.88rem;
+      top: 2.19rem;
+      line-height: 1.56rem;
+      color: rgba(255, 255, 255, 1);
+      font-size: 1.13rem;
+      text-align: left;
+      font-family: Times New Roman-regular;
+
+      button {
+        margin-top: 1.44rem;
+        width: 5.25rem;
+        height: 1.75rem;
+        border: 1px solid rgba(255, 255, 255, 1);
+        line-height: 1.06rem;
+        border-radius: 0.38rem;
+        background-color: rgba(255, 255, 255, 1);
+        color: rgba(9, 52, 74, 1);
+        font-size: 0.75rem;
+        text-align: center;
+        font-family: -regular;
+      }
+    }
+  }
+
+  .card_list {
+    p {
+      line-height: 1.44rem;
+      color: rgba(16, 16, 16, 1);
+      font-size: 1rem;
+      text-align: left;
+      font-family: Times New Roman-regular;
+      margin-bottom: 0.38rem;
+    }
+
+    .card_content {
+      display: flex;
+      flex-wrap: wrap;
+      justify-content: space-between;
+      /* margin-top: 1.25rem; */
+    }
+
+    .card_item {
+      width: 9.88rem;
+      height: 13.75rem;
+      background-color: rgba(255, 255, 255, 1);
+      box-shadow: 0rem 0.06rem 0.88rem 0rem rgba(0, 1, 130, 0.08);
+      border-radius: 0.75rem;
+      overflow: hidden;
+      margin-bottom: 1rem;
+
+      img {
+        width: 100%;
+        height: 9.75rem;
+        object-fit: cover;
+      }
+
+      p {
+        margin: 0.63rem 0 0 0.63rem;
+        line-height: 1.25rem;
+        color: rgba(16, 16, 16, 1);
+        font-size: 0.88rem;
+        text-align: left;
+        font-family: PingFangSC-medium;
+      }
+
+      span {
+        line-height: 1.06rem;
+        color: rgba(102, 102, 102, 1);
+        font-size: 0.75rem;
+        text-align: left;
+        font-family: PingFangSC-regular;
+        margin-left: 0.63rem;
+      }
+    }
+  }
+}
+</style>

+ 259 - 0
src/views/ResultView.vue

@@ -0,0 +1,259 @@
+<script setup lang="ts">
+import {
+  ref,
+  reactive,
+  toRefs,
+  onMounted,
+  watch,
+  computed,
+  onBeforeUnmount,
+} from "vue";
+import NavBarPage from "../components/NavBarPage.vue";
+import randarPage from "./result/randarPage.vue";
+import { useRoute } from "vue-router";
+import { fetchTaskScore } from "./utils/api";
+import { Toast } from "vant";
+
+const route = useRoute();
+
+const score = ref(0);
+const data = ref(null);
+const scoresList = ref(null);
+const taskId = computed(() => route.query.conversationId); // 会话ID "2c64eb8b69be432ca0bb9ae55bc78def"
+const randarlist = ref([0, 0, 0, 0]);
+
+const nameMapping = {
+  introduction_profession: "自我介绍专业性",
+  demand_accuracy: "挖掘需求准确性",
+  product_advantages: "清晰表达产品优势",
+  attract_interest: "吸引客户兴趣",
+  penalty_points: "扣分项",
+};
+
+const handleResult = async () => {
+  let res;
+  let shouldContinue = true; // 控制是否继续调用接口的标志
+
+  while (shouldContinue) {
+    res = await fetchTaskScore(taskId.value);
+
+    if (res.code !== 200) {
+      // 当返回错误时,停止调用接口
+      shouldContinue = false;
+    } else if (res.body && Object.keys(res.body).length !== 0) {
+      // 当res.body有值时,停止调用接口
+      data.value = res.body;
+      shouldContinue = false;
+    } else {
+      await new Promise((resolve) => setTimeout(resolve, 3000));
+    }
+  }
+  if (res && res.code === 200 && res.body) {
+    const scores = extractScores(res.body);
+    score.value = scores.reduce((sum, score) => sum + score, 0);
+    scoresList.value = Object.keys(res.body).reduce((acc, key) => {
+      //   if (key !== "penalty_points") {
+      acc[key] = {
+        ...res.body[key],
+        name: nameMapping[key], // 添加 name 字段
+      };
+      //   }
+      return acc;
+    }, {});
+
+    const obj = extractScores(scoresList.value);
+    randarlist.value = Object.values(obj);
+  }
+};
+
+// 处理“分”字
+const extractScores = (data) => {
+  return Object.values(data).map((item) => {
+    // 去除可能的"分"字,然后转换为整数
+    return parseInt(item.score.replace("分", ""), 10);
+  });
+};
+
+onMounted(() => {
+  handleResult();
+  window.addEventListener("popstate", function (event) {
+    // 执行一些操作,例如关闭当前页面或跳转到其他页面
+    window.location.href = "/#/home";
+  });
+});
+
+onBeforeUnmount(() => {
+  window.removeEventListener("popstate", function (event) {
+    // 执行一些操作,例如关闭当前页面或跳转到其他页面
+    window.location.href = "/#/home";
+  });
+});
+</script>
+<template>
+  <div class="result" style="overflow: hidden">
+    <NavBarPage title="考试" type="home" />
+    <div style="height: 46px"></div>
+    <div class="score">
+      <span>最终得分</span>
+      <p class="score-num">
+        <span>{{ score }}</span
+        >分
+      </p>
+    </div>
+    <div style="height: 9.06rem; visibility: hidden"></div>
+    <div class="content">
+      <div style="width: 100vw; height: 19.06rem">
+        <randarPage :randarList="randarlist" />
+      </div>
+      <div class="standard">
+        <div class="standard-title">
+          <img src="../assets/robot.jpg" alt="" />
+          <span>评分标准</span>
+        </div>
+        <div
+          class="standard-content"
+          v-for="(item, key) in scoresList"
+          :key="key"
+        >
+          <div class="standard-subtitle">
+            <span class="pro"
+              >{{
+                item.name == "扣分项" ? item.name : `${item.name}(25分)`
+              }}:</span
+            >
+            <span>{{ item.score }}</span>
+          </div>
+          <p>
+            <span>得分原因:</span>
+            <span>{{ item.reason }}</span>
+          </p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="less">
+html,
+body {
+  margin: 0;
+  padding: 0;
+  height: 100vh;
+  overflow: hidden;
+  /* 禁止页面整体滚动 */
+}
+
+.result {
+  width: 100%;
+  height: 100%;
+  /* height: 100vh; */
+  overflow: hidden;
+}
+
+.score {
+  position: fixed;
+  z-index: 999;
+  width: 100%;
+  height: 9.06rem;
+  background-color: rgba(243, 249, 255, 1);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+
+  span {
+    display: flex;
+    width: 5.06rem;
+    height: 2.88rem;
+    font-weight: bold;
+    line-height: 2.88rem;
+    color: rgba(64, 149, 229, 1);
+    font-size: 1rem;
+    text-align: center;
+    font-family: Times New Roman-regular;
+  }
+
+  .score-num {
+    display: flex;
+    align-items: last baseline;
+    color: rgba(64, 149, 229, 1);
+    font-size: 1rem;
+
+    span {
+      display: block;
+      width: 5.06rem;
+      height: 3.31rem;
+      line-height: 4.25rem;
+      font-size: 3rem;
+      text-align: center;
+      font-family: Times New Roman-regular;
+    }
+  }
+}
+
+.content {
+  width: 100%;
+  height: 100%;
+  overflow: scroll;
+}
+
+.standard-title {
+  display: flex;
+  align-items: center;
+  padding: 0 0.94rem;
+  margin-bottom: 0.63rem;
+
+  img {
+    width: 1.5rem;
+    height: 1.5rem;
+  }
+
+  span {
+    display: inline-block;
+    width: 4rem;
+    height: 1.44rem;
+    line-height: 1.44rem;
+    margin-left: 0.33rem;
+    color: rgba(64, 149, 229, 1);
+    font-size: 1rem;
+    text-align: center;
+    font-family: Times New Roman-regular;
+  }
+}
+
+.standard-subtitle {
+  padding: 0.56rem 1rem;
+  height: 1.06rem;
+  font-size: 0.88rem;
+  font-family: Times New Roman-regular;
+
+  span {
+    color: rgba(64, 149, 229, 1);
+    font-weight: 600;
+  }
+
+  .pro {
+    display: inline-block;
+    height: 1.06rem;
+    line-height: 1.06rem;
+    padding: 0 0.3rem;
+    font-weight: normal;
+    color: rgba(102, 102, 102, 1);
+    border-left: 0.3rem solid rgba(64, 149, 229, 1);
+  }
+}
+
+.standard-content p {
+  padding: 0 1.63rem;
+  margin-bottom: 0.31rem;
+  line-height: 1.25rem;
+  color: rgba(102, 102, 102, 1);
+  font-size: 0.88rem;
+  text-align: left;
+  font-family: SourceHanSansSC-bold;
+}
+
+.standard-content p span:nth-child(1) {
+  font-weight: 600;
+}
+</style>

+ 48 - 0
src/views/components/BG.vue

@@ -0,0 +1,48 @@
+<script setup>
+import { ref } from "vue";
+
+const images = ["image0.jpg","image0.jpg","image0.jpg"];
+const currentIndex = ref(2);
+
+setInterval(() => {
+  currentIndex.value = (currentIndex.value + 1) % images.length;
+}, 10000);
+</script>
+
+<template>
+  <div class="bg">
+    <img
+      :class="currentIndex === index ? 'active' : ''"
+      v-for="(item, index) in images"
+      :key="item"
+      :src="item"
+    />
+  </div>
+</template>
+
+<style scoped>
+.bg {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.bg img {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  transition: all 0.5s ease-in-out;
+  opacity: 0;
+}
+
+.bg img.active {
+  opacity: 1;
+}
+</style>

+ 353 - 0
src/views/components/BottomArea.vue

@@ -0,0 +1,353 @@
+<script setup>
+import { ref, onMounted, onBeforeUnmount } from "vue";
+import { KeyboardVoiceOutlined, KeyboardAltOutlined } from "@vicons/material";
+import axios from "axios";
+import { Icon } from "@vicons/utils";
+import Recorder from "recorder-core";
+import "recorder-core/src/engine/wav";
+import "recorder-core/src/extensions/frequency.histogram.view";
+import "recorder-core/src/extensions/lib.fft";
+
+const emit = defineEmits(["startRecord", "stopRecord"]);
+
+const isTalking = ref(false);
+const sessionId = ref("");
+
+const userTalk = ref("");
+
+let rec;
+let testSampleRate = ref(16000);
+let testBitRate = ref(16);
+let SendInterval = 300; // 控制实时传输间隔
+
+let realTimeSendTryType,
+  realTimeSendTryEncBusy,
+  realTimeSendTryTime = 0,
+  realTimeSendTryNumber,
+  transferUploadNumberMax,
+  realTimeSendTryChunk,
+  voicePkgSeq = 0,
+  wave = null;
+
+const RealTimeSendTryReset = (type) => {
+  realTimeSendTryType = type;
+  realTimeSendTryTime = 0;
+  realTimeSendTryEncBusy = 0;
+  realTimeSendTryNumber = 0;
+  transferUploadNumberMax = 0;
+  realTimeSendTryChunk = null;
+};
+
+const RealTimeSendTry = (buffers, bufferSampleRate, isClose) => {
+  const t1 = Date.now();
+  if (realTimeSendTryTime === 0) {
+    realTimeSendTryTime = t1;
+    realTimeSendTryEncBusy = 0;
+    realTimeSendTryNumber = 0;
+    transferUploadNumberMax = 0;
+    realTimeSendTryChunk = null;
+  }
+
+  if (!isClose && t1 - realTimeSendTryTime < SendInterval) {
+    return; // 控制缓冲达到指定间隔才进行传输
+  }
+
+  realTimeSendTryTime = t1;
+  const number = ++realTimeSendTryNumber;
+
+  let pcm = [],
+    pcmSampleRate = 0;
+  if (buffers.length > 0) {
+    // 借用SampleData函数进行数据的连续处理,采样率转换是顺带的,得到新的pcm数据
+    const chunk = Recorder.SampleData(
+      buffers,
+      bufferSampleRate,
+      testSampleRate.value,
+      realTimeSendTryChunk,
+      { frameType: isClose ? "" : realTimeSendTryType }
+    );
+
+    for (
+      let i = realTimeSendTryChunk ? realTimeSendTryChunk.index : 0;
+      i < chunk.index;
+      i++
+    ) {
+      buffers[i] = null; // 清理已处理完的缓冲数据
+    }
+
+    realTimeSendTryChunk = chunk;
+    pcm = chunk.data;
+    pcmSampleRate = chunk.sampleRate;
+  }
+
+  // 如果没有新数据,或者结束时数据量太小,不能进行转码
+  if (pcm.length === 0 || (isClose && pcm.length < 2000)) {
+    TransferUpload(number, null, 0, null, isClose);
+    return;
+  }
+
+  // 实时编码队列阻塞处理
+  if (!isClose) {
+    if (realTimeSendTryEncBusy >= 2) {
+      console.log("编码队列阻塞,已丢弃一帧", 1);
+      return;
+    }
+  }
+  realTimeSendTryEncBusy++;
+
+  const encStartTime = Date.now();
+  const recMock = Recorder({
+    type: realTimeSendTryType,
+    sampleRate: testSampleRate.value, // 采样率
+    bitRate: testBitRate.value, // 比特率
+  });
+
+  recMock.mock(pcm, pcmSampleRate);
+  recMock.stop(
+    (blob, duration) => {
+      if (realTimeSendTryEncBusy) realTimeSendTryEncBusy--;
+      blob.encTime = Date.now() - encStartTime;
+      TransferUpload(number, blob, duration, recMock, isClose);
+    },
+    (msg) => {
+      if (realTimeSendTryEncBusy) realTimeSendTryEncBusy--;
+      console.log("不应该出现的错误:" + msg, 1);
+    }
+  );
+};
+
+const TransferUpload = (number, blobOrNull, duration, blobRec, isClose) => {
+  transferUploadNumberMax = Math.max(transferUploadNumberMax, number);
+  if (blobOrNull) {
+    let blob = blobOrNull;
+    let encTime = blob.encTime;
+    // 发送数据的方式一:Base64文本发送
+    let reader = new FileReader();
+    reader.onloadend = () => {
+      let base64 = (/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result) || [])[1];
+      // 可以实现 WebSocket send(base64), WebRTC send(base64), XMLHttpRequest send(base64)
+      asrPost("SESSION_IN", base64);
+    };
+    reader.readAsDataURL(blob);
+  }
+
+  if (isClose) {
+    console.log(
+      "No." +
+        (number < 100 ? ("000" + number).substr(-3) : number) +
+        ":已停止传输"
+    );
+  }
+};
+const makeRandomString = (len) => {
+  len = len || 32;
+  let $chars =
+    "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
+  let maxPos = $chars.length;
+  let pwd = "";
+  for (let i = 0; i < len; i++) {
+    pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
+  }
+  return pwd + new Date().getTime();
+};
+// 语音识别
+const asrPost = async (eventCodeType = "SESSION_IN", base64) => {
+  const res = await axios.post(
+    `/openapi-stg/ai/voice/asr/v1`,
+    {
+      sessionId: sessionId.value,
+      eventCodeType,
+      voicePkgSeq: ++voicePkgSeq,
+      audio: base64,
+      format: 16000,
+      encoding: "WAV",
+    },
+    {
+      headers: {
+        "X-Ai-Asr-Appid": "2b1317fb5b284b308dc90a6fdeae6c4e",
+      },
+    }
+  );
+
+  console.log(res.data.body.asrResultText);
+
+  userTalk.value = userTalk.value + res.data.body.asrResultText;
+};
+
+onMounted(() => {
+  sessionId.value = makeRandomString(5);
+  if (rec) {
+    rec.close();
+  }
+
+  rec = Recorder({
+    type: "unknown",
+    onProcess: (buffers, powerLevel, bufferDuration, bufferSampleRate) => {
+      RealTimeSendTry(buffers, bufferSampleRate, false);
+      wave &&
+        buffers[buffers.length - 1] &&
+        wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
+    },
+  });
+
+  const t = setTimeout(() => {
+    console.log("无法录音:权限请求被忽略(超时假装手动点击了确认对话框)", 1);
+  }, 8000);
+
+  rec.open(
+    () => {
+      if (Recorder.FrequencyHistogramView) {
+        wave = Recorder.FrequencyHistogramView({
+          elem: ".recwave",
+          lineCount: 10,
+          position: 0,
+          minHeight: 1,
+          fallDuration: 400,
+          stripeEnable: false,
+          mirrorEnable: true,
+          linear: [0, "#fff", 1, "#fff"],
+        });
+      }
+
+      clearTimeout(t);
+    },
+    (msg, isUserNotAllow) => {
+      clearTimeout(t);
+      console.log(
+        (isUserNotAllow ? "UserNotAllow," : "") + "无法录音:" + msg,
+        1
+      );
+    }
+  );
+  //console.log(document.querySelector(".recwave"));
+  //const div = document.createElement("div");
+  // const div = document.querySelector(".wave");
+  // div.innerHTML =
+  //   '<div style="height:100px;width:100%;" class="recwave"></div>';
+  //document.body.prepend(div);
+});
+
+onBeforeUnmount(() => {
+  if (rec) rec.close();
+});
+
+const startRecording = async (e) => {
+  e.preventDefault();
+  isTalking.value = true;
+  userTalk.value = "";
+  asrPost("SESSION_BEGIN");
+  rec.start();
+  RealTimeSendTryReset("wav");
+  emit("startRecord");
+};
+
+const stopRecording = async () => {
+  isTalking.value = false;
+  //等待500ms后,在执行以下代码
+  await new Promise((resolve) => setTimeout(resolve, 1000));
+  rec.stop();
+  RealTimeSendTry([], 0, true); // 最后一次发送
+  await asrPost("SESSION_END");
+  voicePkgSeq = 0;
+  emit("stopRecord", userTalk.value);
+};
+</script>
+
+<template>
+  <div
+    :class="`bottom-btn ${isTalking ? 'touch-start' : ''}`"
+    @touchstart="startRecording"
+    @touchend="stopRecording"
+  >
+    <div class="btn">
+      <div class="text">按住说话</div>
+      <Icon class="icon" color="white"><KeyboardAltOutlined /></Icon>
+    </div>
+    <div class="talk-item">
+      <div class="wave">
+        <div class="recwave" style="height: 100%; width: 100%"></div>
+      </div>
+      <Icon class="icon" color="#1989fa"><KeyboardVoiceOutlined /></Icon>
+      <div>松手发送</div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.bottom-btn {
+  position: fixed;
+  bottom: 0;
+  right: 0;
+  width: 120vw;
+  left: -10vw;
+  display: flex;
+  justify-content: center;
+  height: 80px;
+  background: rgba(255, 255, 255, 0);
+  transition: height 0.1s, background 0.1s, border-radius 0.1s;
+  padding-bottom: env(safe-area-inset-bottom);
+}
+.bottom-btn.touch-start {
+  background: rgba(255, 255, 255, 0.8);
+  border-radius: 50% 50% 0 0;
+  height: 120px;
+}
+.bottom-btn.touch-start .btn {
+  display: none;
+}
+.bottom-btn.touch-start .talk-item {
+  visibility: visible;
+}
+.btn {
+  width: 90vw;
+  height: 40px;
+  background: rgba(150, 150, 150, 0.5);
+  color: white;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  position: absolute;
+  bottom: calc(20px + env(safe-area-inset-bottom));
+}
+.btn .text {
+  flex-grow: 1;
+  text-align: center;
+}
+.btn .icon {
+  margin-right: 10px;
+}
+
+.talk-item {
+  position: absolute;
+  bottom: calc(40px + env(safe-area-inset-bottom));
+  visibility: hidden;
+  text-align: center;
+  color: #1989fa;
+}
+.talk-item .wave {
+  position: absolute;
+  top: -100px;
+  left: 50%;
+  margin-left: -50px;
+  width: 100px;
+  height: 50px;
+  background: #1989fa;
+  border-radius: 8px;
+}
+.talk-item .wave::after {
+  content: "";
+  position: absolute;
+  bottom: -8px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 0;
+  height: 0;
+  border-left: 8px solid transparent;
+  border-right: 8px solid transparent;
+  border-top: 8px solid rgb(25, 137, 250);
+}
+.talk-item .icon {
+  font-size: 24px;
+}
+</style>

+ 126 - 0
src/views/components/ChatList.vue

@@ -0,0 +1,126 @@
+<script setup>
+import { defineProps, onMounted, nextTick, ref, watch } from "vue";
+
+const scrollRef = ref(null);
+
+const props = defineProps({
+  chatList: Array,
+});
+
+watch(
+  () => props.chatList,
+  () => {
+    scrollToBottom();
+  },
+  { deep: true }
+);
+
+const scrollToBottom = async () => {
+  await nextTick();
+  if (scrollRef.value) scrollRef.value.scrollTop = scrollRef.value.scrollHeight;
+};
+
+defineExpose({
+  scrollToBottom,
+});
+</script>
+
+<template>
+  <div id="scrollRef" ref="scrollRef" class="chat-area">
+    <div v-for="item in chatList" :key="item.id" :class="`chat-${item.type}`">
+      <div v-if="item.text" class="chat-item">
+        {{ item.text }}
+      </div>
+      <div v-if="!item.text" class="loader">
+        <div></div>
+        <div></div>
+        <div></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.chat-area {
+  position: relative;
+  position: fixed;
+  width: 100vw;
+  max-height: 50vh;
+  /* bottom: calc(80px + env(safe-area-inset-bottom)); */
+  bottom: calc(65px + env(safe-area-inset-bottom));
+  padding: 10px;
+  overflow-y: auto;
+  box-sizing: border-box;
+  mask-image: linear-gradient(to top, black 70%, transparent);
+  -mask-image: linear-gradient(to top, black 70%, transparent);
+}
+.chat-area .chat-gpt {
+  display: flex;
+  justify-content: start;
+}
+.chat-area .chat-user {
+  display: flex;
+  justify-content: end;
+}
+.chat-item {
+  color: white;
+  padding: 10px;
+  font-size: 14px;
+  margin-bottom: 10px;
+  display: flex;
+  flex-direction: column;
+  word-break: break-all;
+  position: relative;
+  background: rgba(25, 137, 250, 0.8);
+  max-width: 45%;
+}
+.chat-area .chat-gpt .chat-item {
+  border-radius: 0 4px 4px 4px;
+  background-color: white;
+  color: #333;
+}
+.chat-area .chat-user .chat-item {
+  border-radius: 4px 0 4px 4px;
+}
+
+.loader {
+  display: inline-block;
+  position: relative;
+  height: 40px;
+}
+
+.loader div {
+  display: inline-block;
+  position: absolute;
+  left: 8px;
+  width: 12px;
+  height: 12px;
+  background: white;
+  border-radius: 50%;
+  animation: loading 1s cubic-bezier(0, 0.5, 0.5, 1) infinite;
+}
+
+.loader div:nth-child(1) {
+  left: 8px;
+  animation-delay: -0.24s;
+}
+.loader div:nth-child(2) {
+  left: 32px;
+  animation-delay: -0.12s;
+}
+.loader div:nth-child(3) {
+  left: 56px;
+  animation-delay: 0;
+}
+
+@keyframes loading {
+  0%,
+  80%,
+  100% {
+    transform: scale(0);
+  }
+  40% {
+    transform: scale(1);
+  }
+}
+</style>

+ 105 - 0
src/views/demo/Asr.vue

@@ -0,0 +1,105 @@
+<script setup>
+import Recorder from 'recorder-core';
+import 'recorder-core/src/engine/mp3';
+import 'recorder-core/src/engine/mp3-engine'
+//可选的插件支持项,把需要的插件按需引入进来即可
+//import 'recorder-core/src/extensions/waveview'
+import 'recorder-core/src/extensions/frequency.histogram.view'
+import 'recorder-core/src/extensions/lib.fft'
+
+import { ref } from 'vue';
+
+let rec = null;
+let processTime = 0;
+let wave = null;
+
+const recOpen = (success) => {
+  rec = Recorder({
+    type: "mp3",
+    sampleRate: 16000,
+    bitRate: 16,
+    onProcess: (buffers, powerLevel, bufferDuration, bufferSampleRate) => {
+      processTime = Date.now();
+      wave && wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
+    }
+  });
+
+  rec.open(() => {
+    if (Recorder.FrequencyHistogramView) wave = Recorder.FrequencyHistogramView({ 
+        elem: ".recwave"
+        ,lineCount:20
+		,position:0
+		,minHeight:1
+		,fallDuration:400
+		,stripeEnable:false
+		,mirrorEnable:true
+		,linear:[0,"#0ac",1,"#0ac"] 
+    });
+    success && success();
+  }, (msg, isUserNotAllow) => {
+    console.log((isUserNotAllow ? "UserNotAllow, " : "") + "Unable to record: " + msg);
+  });
+
+    // Create the WaveView element and prepend it to the body
+    const div = document.createElement("div");
+    div.innerHTML = '<div style="height:100px;width:100%;" class="recwave"></div>';
+    document.body.prepend(div);
+};
+
+const startRecording = () => {
+  rec.start();
+
+  rec.watchDogTimer = setInterval(() => {
+    if (!rec || rec.watchDogTimer != rec.watchDogTimer) {
+      clearInterval(rec.watchDogTimer);
+      return;
+    }
+    if (Date.now() < rec.wdtPauseT) return;
+    if (Date.now() - (processTime || startTime) > 1500) {
+      clearInterval(rec.watchDogTimer);
+      console.error(processTime ? "Recording interrupted" : "Recording did not start properly");
+      // Your error handling logic here
+    }
+  }, 1000);
+
+  const startTime = Date.now();
+  rec.wdtPauseT = 0;
+  processTime = 0;
+};
+
+const stopRecording = () => {
+  clearInterval(rec.watchDogTimer);
+  rec.stop((blob, duration) => {
+    const localUrl = (window.URL || webkitURL).createObjectURL(blob);
+    console.log(blob, localUrl, "Duration: " + duration + "ms");
+
+    rec.close();
+    rec = null;
+
+    var audio = document.createElement("audio");
+    document.body.prepend(audio);
+    audio.controls = true;
+    audio.src = localUrl;
+    audio.play();
+  }, (msg) => {
+    console.log("Recording failed: " + msg);
+    rec.close();
+    rec = null;
+  });
+};
+
+recOpen(() => {
+  // Callback after successfully opening recorder
+});
+</script>
+
+<template>
+    <div>
+        <van-button type="primary" @click="startRecording">开始录音</van-button>
+        <van-button type="primary" @click="stopRecording">停止录音</van-button>
+    </div>
+</template>
+
+<style>
+
+</style>

+ 457 - 0
src/views/demo/AsrDemo.vue

@@ -0,0 +1,457 @@
+<template>
+  <div class="no-select">
+    <!-- <button @mousedown="recStart('mp3')" @mouseup="recStop">
+      mp3按住录音并实时asr
+    </button> -->
+    <van-button
+      style="margin: 20px 0"
+      type="primary"
+      size="large"
+      @touchstart="recStart('wav')"
+      @touchend="recStop"
+      >手机端长按</van-button
+    >
+    <van-button
+      type="primary"
+      size="large"
+      @mousedown="recStart('wav')"
+      @mouseup="recStop"
+      >电脑端鼠标长按</van-button
+    >
+    <!-- <button @mousedown="recStart('pcm')" @mouseup="recStop">
+      pcm按住录音并实时asr(不支持播放)
+    </button> -->
+    <div>{{ userTalk }}</div>
+    <div>{{ text }}</div>
+    <!-- <div class="audioPlay"></div>
+    <div class="progress"></div> -->
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onBeforeUnmount } from "vue";
+import axios from "axios";
+import Recorder from "recorder-core";
+import "recorder-core/src/engine/mp3";
+import "recorder-core/src/engine/mp3-engine";
+import "recorder-core/src/engine/wav";
+import "recorder-core/src/engine/pcm";
+//可选的插件支持项,把需要的插件按需引入进来即可
+//import 'recorder-core/src/extensions/waveview'
+import "recorder-core/src/extensions/frequency.histogram.view";
+import "recorder-core/src/extensions/lib.fft";
+
+const userTalk = ref("");
+
+let rec;
+let testOutputWavLog = ref(false); // 用于调试是否输出wav格式
+let testSampleRate = ref(16000);
+let testBitRate = ref(16);
+let SendInterval = 300; // 控制实时传输间隔
+
+let realTimeSendTryType,
+  realTimeSendTryEncBusy,
+  realTimeSendTryTime = 0,
+  realTimeSendTryNumber,
+  transferUploadNumberMax,
+  realTimeSendTryChunk,
+  voicePkgSeq = 0;
+
+const RealTimeSendTryReset = (type) => {
+  realTimeSendTryType = type;
+  realTimeSendTryTime = 0;
+  realTimeSendTryEncBusy = 0;
+  realTimeSendTryNumber = 0;
+  transferUploadNumberMax = 0;
+  realTimeSendTryChunk = null;
+};
+
+const RealTimeSendTry = (buffers, bufferSampleRate, isClose) => {
+  const t1 = Date.now();
+  if (realTimeSendTryTime === 0) {
+    realTimeSendTryTime = t1;
+    realTimeSendTryEncBusy = 0;
+    realTimeSendTryNumber = 0;
+    transferUploadNumberMax = 0;
+    realTimeSendTryChunk = null;
+  }
+
+  if (!isClose && t1 - realTimeSendTryTime < SendInterval) {
+    return; // 控制缓冲达到指定间隔才进行传输
+  }
+
+  realTimeSendTryTime = t1;
+  const number = ++realTimeSendTryNumber;
+
+  let pcm = [],
+    pcmSampleRate = 0;
+  if (buffers.length > 0) {
+    // 借用SampleData函数进行数据的连续处理,采样率转换是顺带的,得到新的pcm数据
+    const chunk = Recorder.SampleData(
+      buffers,
+      bufferSampleRate,
+      testSampleRate.value,
+      realTimeSendTryChunk,
+      { frameType: isClose ? "" : realTimeSendTryType }
+    );
+
+    for (
+      let i = realTimeSendTryChunk ? realTimeSendTryChunk.index : 0;
+      i < chunk.index;
+      i++
+    ) {
+      buffers[i] = null; // 清理已处理完的缓冲数据
+    }
+
+    realTimeSendTryChunk = chunk;
+    pcm = chunk.data;
+    pcmSampleRate = chunk.sampleRate;
+  }
+
+  // 如果没有新数据,或者结束时数据量太小,不能进行转码
+  if (pcm.length === 0 || (isClose && pcm.length < 2000)) {
+    TransferUpload(number, null, 0, null, isClose);
+    return;
+  }
+
+  // 实时编码队列阻塞处理
+  if (!isClose) {
+    if (realTimeSendTryEncBusy >= 2) {
+      console.log("编码队列阻塞,已丢弃一帧", 1);
+      return;
+    }
+  }
+  realTimeSendTryEncBusy++;
+
+  const encStartTime = Date.now();
+  const recMock = Recorder({
+    type: realTimeSendTryType,
+    sampleRate: testSampleRate.value, // 采样率
+    bitRate: testBitRate.value, // 比特率
+  });
+
+  recMock.mock(pcm, pcmSampleRate);
+  recMock.stop(
+    (blob, duration) => {
+      if (realTimeSendTryEncBusy) realTimeSendTryEncBusy--;
+      blob.encTime = Date.now() - encStartTime;
+      TransferUpload(number, blob, duration, recMock, isClose);
+    },
+    (msg) => {
+      if (realTimeSendTryEncBusy) realTimeSendTryEncBusy--;
+      console.log("不应该出现的错误:" + msg, 1);
+    }
+  );
+};
+
+const TransferUpload = (number, blobOrNull, duration, blobRec, isClose) => {
+  transferUploadNumberMax = Math.max(transferUploadNumberMax, number);
+  if (blobOrNull) {
+    let blob = blobOrNull;
+    let encTime = blob.encTime;
+    // 发送数据的方式一:Base64文本发送
+    let reader = new FileReader();
+    reader.onloadend = () => {
+      let base64 = (/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result) || [])[1];
+      // 可以实现 WebSocket send(base64), WebRTC send(base64), XMLHttpRequest send(base64)
+      asrPost("SESSION_IN", base64);
+    };
+    reader.readAsDataURL(blob);
+
+    // 发送数据的方式二:Blob二进制发送
+    // 可以实现 WebSocket send(blob), WebRTC send(blob), XMLHttpRequest send(blob)
+
+    const numberFail =
+      number < transferUploadNumberMax
+        ? '<span style="color:red">顺序错乱的数据,如果要求不高可以直接丢弃,或者调大SendInterval试试</span>'
+        : "";
+    const logMsg =
+      "No." +
+      (number < 100 ? ("000" + number).substr(-3) : number) +
+      numberFail;
+
+    console.log(
+      blob,
+      duration,
+      blobRec,
+      logMsg + "花" + ("___" + encTime).substr(-3) + "ms"
+    );
+
+    // 插入html
+    const childDiv = document.createElement("div");
+    const button = document.createElement("button");
+
+    childDiv.textContent = `${logMsg + ("___" + encTime).substr(-3) + "ms"}`;
+    button.textContent = "播放";
+    button.addEventListener("click", () => {
+      recPlay(blob);
+    });
+
+    childDiv.appendChild(button);
+    //document.querySelector(".progress").appendChild(childDiv);
+  }
+
+  if (isClose) {
+    console.log(
+      "No." +
+        (number < 100 ? ("000" + number).substr(-3) : number) +
+        ":已停止传输"
+    );
+  }
+};
+
+const recStart = async (type) => {
+  userTalk.value = "";
+  text.value = "";
+  lastStrIndex = 0;
+  await asrPost("SESSION_BEGIN");
+  rec.start();
+  RealTimeSendTryReset(type);
+};
+
+const recStop = async () => {
+  rec.stop();
+  RealTimeSendTry([], 0, true); // 最后一次发送
+  await asrPost("SESSION_END");
+  voicePkgSeq = 0;
+  chatGpt();
+};
+
+const recPlay = (blob) => {
+  const audioPlayElement = document.querySelector(".audioPlay");
+  audioPlayElement.innerHTML = "";
+  const audio = document.createElement("audio");
+  audio.controls = true;
+  audioPlayElement.appendChild(audio);
+  audio.src = (window.URL || webkitURL).createObjectURL(blob);
+  audio.play();
+
+  setTimeout(() => {
+    (window.URL || webkitURL).revokeObjectURL(audio.src);
+  }, 5000);
+};
+
+const asrPost = async (eventCodeType = "SESSION_IN", base64) => {
+  const wgToken = `C1qziFGlIv3tnCQxcFaStrLuZOO2ZZXjN7FB_G0WlrOLjclfObbSaXAKzl4RWwQBf_0Zhsm0CoVvdVsYMD18iM_LJrxtn7LHJJQuF9UoUuF3fvqOwrG4EF6Z4GahtxtQ2oeaPQBBNKlgVW1xUW7tkhEdXWqzDHPA_I_91Lczk0PI4guhx1c88Hst4-HI8pdMbiUdEJzj3d3a2W06Fa0XA9Q0taAwaRd1k9jUrDVyj9GfS84_SIgJF4SPjWVfsraV79ieb_StgRcUwZjbscGPMlifnJD6F00wwNbxG7AuCHbl3EtMfSed1vuVx3AsizIckwzIVSVRpOGw72cdAMui-I6es9Ozj2ITzSa5KgyXEpX4qCHF1VcCM1wlHLQ_5hLnJIi4r8NsnJPsxMYrTw`;
+  const res = await axios.post(
+    `https://fls-ai-stg-sit.pingan.com.cn/openapi/ai/voice/asr/v1?channelId=ASP-TEST&sceneId=ASP-TEST&token=${wgToken}`,
+    {
+      sessionId: "N7FB_G0WlrOLjc",
+      eventCodeType,
+      voicePkgSeq: ++voicePkgSeq,
+      audio: base64,
+      format: 16000,
+      encoding: "WAV",
+    },
+    {
+      headers: {
+        "X-Ai-Asr-Appid": "2b1317fb5b284b308dc90a6fdeae6c4e",
+      },
+    }
+  );
+
+  console.error(res.data.body.asrResultText);
+
+  userTalk.value = userTalk.value + res.data.body.asrResultText;
+};
+
+onMounted(() => {
+  if (rec) {
+    rec.close();
+  }
+
+  rec = Recorder({
+    type: "unknown",
+    onProcess: (buffers, powerLevel, bufferDuration, bufferSampleRate) => {
+      RealTimeSendTry(buffers, bufferSampleRate, false);
+    },
+  });
+
+  const t = setTimeout(() => {
+    console.log("无法录音:权限请求被忽略(超时假装手动点击了确认对话框)", 1);
+  }, 8000);
+
+  rec.open(
+    () => {
+      clearTimeout(t);
+    },
+    (msg, isUserNotAllow) => {
+      clearTimeout(t);
+      console.log(
+        (isUserNotAllow ? "UserNotAllow," : "") + "无法录音:" + msg,
+        1
+      );
+    }
+  );
+});
+
+onBeforeUnmount(() => {
+  if (rec) rec.close();
+});
+
+////////
+
+import PQueue from "p-queue";
+
+const text = ref("");
+
+// 创建一个队列实例,设置并发数为 1
+const queue = new PQueue({ concurrency: 1 });
+const queue1 = new PQueue({ concurrency: 1 });
+
+// 需要tts的文本
+const messageQueue = [];
+
+// lastStrIndex 用于记录上一个字符串的结束位置
+let lastStrIndex = 0;
+
+// 将字符串根据标点符号断句分割,并添加到messageQueue中
+const splitMessage = async (str) => {
+  const punctuation = ["。", "!", "?", ";", ":", ","];
+
+  for (let i = lastStrIndex; i < str.length; i++) {
+    if (punctuation.includes(str[i])) {
+      const message = str.slice(lastStrIndex, i + 1);
+      console.log(message, "==========");
+      await playTTS(message);
+      lastStrIndex = i + 1; // 更新上一个字符串的结束位置
+    }
+  }
+};
+
+const playTTS = async (ttsMessage) => {
+  //const ttsMessage =
+  //  "这个错误通常表示浏览器无法识别音频数据的格式或编码,导致无法加载音频源。为了解决这个问题,你可以尝试使用静态的 WAV 格式音频文件以确保能够正常播放,并且避免直接处理原始的音频数据。以下是一个示例代码来加载外部的 WAV 格式音频文件:"; // 替换为你想要播报的文本
+
+  try {
+    const wgToken = `C1qziFGlIv3tnCQxcFaStrLuZOO2ZZXjN7FB_G0WlrOLjclfObbSaXAKzl4RWwQBf_0Zhsm0CoVvdVsYMD18iM_LJrxtn7LHJJQuF9UoUuF3fvqOwrG4EF6Z4GahtxtQ2oeaPQBBNKlgVW1xUW7tkhEdXWqzDHPA_I_91Lczk0PI4guhx1c88Hst4-HI8pdMbiUdEJzj3d3a2W06Fa0XA9Q0taAwaRd1k9jUrDVyj9GfS84_SIgJF4SPjWVfsraV79ieb_StgRcUwZjbscGPMlifnJD6F00wwNbxG7AuCHbl3EtMfSed1vuVx3AsizIckwzIVSVRpOGw72cdAMui-I6es9Ozj2ITzSa5KgyXEpX4qCHF1VcCM1wlHLQ_5hLnJIi4r8NsnJPsxMYrTw`;
+    const res = await axios.post(
+      `https://fls-ai-stg-sit.pingan.com.cn/openapi/ai/voice/tts/v2?channelId=ASP-TEST&sceneId=ASP-TEST&token=${wgToken}`,
+      { sessionId: "N7FB_G0WlrOLjc", text: ttsMessage },
+      {
+        responseType: "arraybuffer",
+        headers: {
+          "X-Ai-TTS-Appid": "2b1317fb5b284b308dc90a6fdeae6c4e",
+        },
+      }
+    );
+    console.log(res.data);
+
+    queue1.add(async () => {
+      // 播放获取到的音频
+      await playAudio(res.data);
+    });
+  } catch (error) {
+    console.error("Error calling TTS API:", error);
+  }
+};
+
+// const playAudio = async (audioData) => {
+//   const blob = new Blob([audioData], { type: "audio/wav" });
+//   const url = URL.createObjectURL(blob);
+//   const audio = new Audio(url);
+//   audio.onended = () => URL.revokeObjectURL(url);
+//   audio.onerror = (error) => console.error("Audio playback error:", error);
+//   audio
+//     .play()
+//     .then(() => console.log("Audio playing"))
+//     .catch((error) => console.error("Error playing audio:", error));
+// };
+
+const playAudio = (audioData, options = {}) => {
+  return new Promise((resolve, reject) => {
+    const blob = new Blob([audioData], { type: "audio/wav" });
+    const url = URL.createObjectURL(blob);
+    const audio = new Audio(url);
+
+    if (options.volume) {
+      audio.volume = options.volume; // 设置音量
+    }
+
+    audio.onended = () => {
+      URL.revokeObjectURL(url);
+      resolve();
+    };
+
+    audio.onerror = (error) => {
+      URL.revokeObjectURL(url);
+      reject(error);
+    };
+
+    audio
+      .play()
+      .then(() => console.log("Audio playing"))
+      .catch(reject);
+  });
+};
+
+const chatGpt = async () => {
+  const downloadUrl =
+    "https://fls-ai.pingan.com.cn/openapi/ai/llm/forward/api/ai/nlp/dialogue?token=mVcexzY_mjtGAL5_exPlmAyfOJxuuEthWY1mk9tUFC_HwceY58uRZ2WDhz7-ttexCdUtFN8C7V636_jIq6fzaSfqIj8OQyhUPKPMa2eZjLlblT77ySqBt_lYM6iEAhrj7-raGmySMmkLS4Rqh651Ak2tqmUbjS64cqv5ofMsuadOCg1J-CtLFt7NeSoU4N3Kpm5MJ_4sOFBhQGfBym88dcwxosFl9LbvhpyleXFf6fOZkkOj0l2X8Nr2pfNjYs3_VOmCQxrxXh1XZ_a1v9qj5_rA9k9wGNNQfmr2JwJTUT4V9NwtNq94gNFt8C0J6MWKVE2eyr25Rke8tkKu3CGNNmspmEFpr6LavPlaWnWOIh9CRJ1cIDB70pg_JD2l0nPTkPbtaTQaIGTz"; // 替换为实际的下载URL
+
+  try {
+    const response = await axios.post(
+      downloadUrl,
+      {
+        conversationId: "1976cfe3a5174f9ba768677f789cad7e",
+        content: `${userTalk.value} 请输出纯文本的回答,不要使用markdown输出`,
+        messageId: Date.now(),
+        applicationId: "",
+      },
+      {
+        headers: {
+          "Team-Id": "123456",
+          Authorization: "9833ae306bde47f8b00b20c18ec809ae",
+          "Content-Type": "application/json",
+        },
+        onDownloadProgress: ({ event }) => {
+          //console.log(event);
+          const xhr = event.target;
+          const { responseText } = xhr;
+          //   // Always process the final line
+          //   const lastIndex = responseText.lastIndexOf(
+          //     "\n",
+          //     responseText.length - 2
+          //   );
+          //   let chunk = responseText;
+
+          //   if (lastIndex !== -1) chunk = responseText.substring(lastIndex);
+          //console.log(responseText, "chunk====");
+          text.value = responseText;
+          queue.add(async () => {
+            await splitMessage(responseText);
+          });
+        },
+      }
+    );
+    console.log(response);
+    console.log(messageQueue, "messageQueue=======0000");
+  } catch (error) {
+    console.error("下载文件时出错:", error);
+  }
+};
+</script>
+
+<style scoped>
+.no-select {
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+* {
+  -webkit-touch-callout: none;
+
+  -webkit-user-select: none;
+
+  -khtml-user-select: none;
+
+  -moz-user-select: none;
+
+  -ms-user-select: none;
+
+  user-select: none;
+
+  -o-user-select: none;
+}
+</style>

+ 55 - 0
src/views/demo/Home.vue

@@ -0,0 +1,55 @@
+<script setup>
+import { ref } from "vue";
+// 用于sse请求 对sse进行了封装 支持post携带参数请求 文档: https://www.npmjs.com/package/@microsoft/fetch-event-source
+import { fetchEventSource } from "@microsoft/fetch-event-source";
+
+const images = ["image0.png", "image1.png", "image2.png", "image3.png"];
+
+const currentIndex = ref(0);
+
+setInterval(() => {
+  currentIndex.value = (currentIndex.value + 1) % images.length;
+}, 5000); // 切换图片间隔时间(毫秒)
+</script>
+
+<template>
+  <div class="main">
+    <div class="bg">
+      <img
+        :class="currentIndex === index ? 'active' : ''"
+        v-for="(item, index) in images"
+        :key="item"
+        :src="item"
+      />
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.main {
+  width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+}
+.bg {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.bg img {
+  position: absolute;
+  top: 0;
+  left: 0;
+  transition: all 0.5s ease-in-out;
+  opacity: 0;
+}
+
+.bg img.active {
+  opacity: 1;
+}
+</style>

+ 51 - 0
src/views/demo/Pic.vue

@@ -0,0 +1,51 @@
+<script setup>
+import { ref, computed } from 'vue';
+
+const images = [
+  'image1.png',
+  'image2.png',
+  'image3.png'
+];
+
+const currentIndex = ref(0);
+
+setInterval(() => {
+  currentIndex.value = (currentIndex.value + 1) % images.length;
+}, 5000); // 切换图片间隔时间(毫秒)
+
+const currentImage = computed(() => images[currentIndex.value]);
+
+</script>
+
+<template>
+    <div class="image-gallery">
+        <img :class="currentIndex === index ? 'active' : ''" v-for="(item, index) in images" :key="item" :src="item" alt="Gallery Image" />
+    </div>
+  </template>
+
+<style scoped>
+.image-gallery {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.image-gallery img {
+    position:absolute;
+    top:0;
+    left:0;
+    transition: all 0.5s ease-in-out;
+    opacity: 0;
+}
+
+.image-gallery img.active {
+    opacity: 1;
+}
+
+
+
+</style>

+ 53 - 0
src/views/demo/Sse.vue

@@ -0,0 +1,53 @@
+<script setup>
+import { fetchEventSource } from '@microsoft/fetch-event-source';
+import { ref } from 'vue';
+
+const message = ref('');
+const result = ref('');
+
+/**
+ * Establishes a server-sent event stream to receive real-time messages from the server.
+ *
+ * @param {object} [params={}] - The parameters to be sent to the server.
+ *
+ * @returns {void}
+ */
+const getRealtimeMessage = ()=>{
+  const params = {
+    message: message.value
+  }; 
+  const ctrlAbout = new AbortController();
+  fetchEventSource('/api/events', {
+    method: 'POST',
+    headers: {
+      'Authorization': 'token',
+      'Content-Type': 'application/json', // 文本返回格式
+    },
+    body: JSON.stringify(params),
+    signal: ctrlAbout.signal,
+    openWhenHidden: true, // 在浏览器标签页隐藏时保持与服务器的EventSource连接
+    onmessage(res) {
+      // 操作流式数据
+      result.value += res.data + '\n';
+    },
+    onclose(res) {
+      // 关闭流
+    },
+    onerror(error) {
+      // 返回流报错
+    }
+  })
+}
+</script>
+
+<template>
+    <div>
+        <van-field v-model="message" placeholder="请输入" type="textarea" />
+        <van-button type="primary" @click="getRealtimeMessage">发送</van-button>
+        <van-field v-model="result" type="textarea" />
+    </div>
+</template>
+
+<style scoped>
+
+</style>

+ 151 - 0
src/views/demo/Steam.vue

@@ -0,0 +1,151 @@
+<template>
+  <div>
+    <button @click="downloadFile">说话</button>
+    <div>{{ text }}</div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, watch, ref } from "vue";
+import axios from "axios";
+import PQueue from "p-queue";
+
+const text = ref("");
+
+// 创建一个队列实例,设置并发数为 1
+const queue = new PQueue({ concurrency: 1 });
+const queue1 = new PQueue({ concurrency: 1 });
+
+// 需要tts的文本
+const messageQueue = [];
+
+// lastStrIndex 用于记录上一个字符串的结束位置
+let lastStrIndex = 0;
+
+// 将字符串根据标点符号断句分割,并添加到messageQueue中
+const splitMessage = async (str) => {
+  const punctuation = ["。", "!", "?", ";", ":", ","];
+
+  for (let i = lastStrIndex; i < str.length; i++) {
+    if (punctuation.includes(str[i])) {
+      const message = str.slice(lastStrIndex, i + 1);
+      console.log(message, "==========");
+      await playTTS(message);
+      lastStrIndex = i + 1; // 更新上一个字符串的结束位置
+    }
+  }
+};
+
+const playTTS = async (ttsMessage) => {
+  //const ttsMessage =
+  //  "这个错误通常表示浏览器无法识别音频数据的格式或编码,导致无法加载音频源。为了解决这个问题,你可以尝试使用静态的 WAV 格式音频文件以确保能够正常播放,并且避免直接处理原始的音频数据。以下是一个示例代码来加载外部的 WAV 格式音频文件:"; // 替换为你想要播报的文本
+
+  try {
+    const wgToken = `C1qziFGlIv3tnCQxcFaStrLuZOO2ZZXjN7FB_G0WlrOLjclfObbSaXAKzl4RWwQBf_0Zhsm0CoVvdVsYMD18iM_LJrxtn7LHJJQuF9UoUuF3fvqOwrG4EF6Z4GahtxtQ2oeaPQBBNKlgVW1xUW7tkhEdXWqzDHPA_I_91Lczk0PI4guhx1c88Hst4-HI8pdMbiUdEJzj3d3a2W06Fa0XA9Q0taAwaRd1k9jUrDVyj9GfS84_SIgJF4SPjWVfsraV79ieb_StgRcUwZjbscGPMlifnJD6F00wwNbxG7AuCHbl3EtMfSed1vuVx3AsizIckwzIVSVRpOGw72cdAMui-I6es9Ozj2ITzSa5KgyXEpX4qCHF1VcCM1wlHLQ_5hLnJIi4r8NsnJPsxMYrTw`;
+    const res = await axios.post(
+      `https://fls-ai-stg-sit.pingan.com.cn/openapi/ai/voice/tts/v2?channelId=ASP-TEST&sceneId=ASP-TEST&token=${wgToken}`,
+      { sessionId: "N7FB_G0WlrOLjc", text: ttsMessage },
+      {
+        responseType: "arraybuffer",
+        headers: {
+          "X-Ai-TTS-Appid": "2b1317fb5b284b308dc90a6fdeae6c4e",
+        },
+      }
+    );
+    console.log(res.data);
+
+    queue1.add(async () => {
+      // 播放获取到的音频
+      await playAudio(res.data);
+    });
+  } catch (error) {
+    console.error("Error calling TTS API:", error);
+  }
+};
+
+// const playAudio = async (audioData) => {
+//   const blob = new Blob([audioData], { type: "audio/wav" });
+//   const url = URL.createObjectURL(blob);
+//   const audio = new Audio(url);
+//   audio.onended = () => URL.revokeObjectURL(url);
+//   audio.onerror = (error) => console.error("Audio playback error:", error);
+//   audio
+//     .play()
+//     .then(() => console.log("Audio playing"))
+//     .catch((error) => console.error("Error playing audio:", error));
+// };
+
+const playAudio = (audioData, options = {}) => {
+  return new Promise((resolve, reject) => {
+    const blob = new Blob([audioData], { type: "audio/wav" });
+    const url = URL.createObjectURL(blob);
+    const audio = new Audio(url);
+
+    if (options.volume) {
+      audio.volume = options.volume; // 设置音量
+    }
+
+    audio.onended = () => {
+      URL.revokeObjectURL(url);
+      resolve();
+    };
+
+    audio.onerror = (error) => {
+      URL.revokeObjectURL(url);
+      reject(error);
+    };
+
+    audio
+      .play()
+      .then(() => console.log("Audio playing"))
+      .catch(reject);
+  });
+};
+
+const downloadFile = async () => {
+  const downloadUrl =
+    "https://fls-ai.pingan.com.cn/openapi/ai/llm/forward/api/ai/nlp/dialogue?token=mVcexzY_mjtGAL5_exPlmAyfOJxuuEthWY1mk9tUFC_HwceY58uRZ2WDhz7-ttexCdUtFN8C7V636_jIq6fzaSfqIj8OQyhUPKPMa2eZjLlblT77ySqBt_lYM6iEAhrj7-raGmySMmkLS4Rqh651Ak2tqmUbjS64cqv5ofMsuadOCg1J-CtLFt7NeSoU4N3Kpm5MJ_4sOFBhQGfBym88dcwxosFl9LbvhpyleXFf6fOZkkOj0l2X8Nr2pfNjYs3_VOmCQxrxXh1XZ_a1v9qj5_rA9k9wGNNQfmr2JwJTUT4V9NwtNq94gNFt8C0J6MWKVE2eyr25Rke8tkKu3CGNNmspmEFpr6LavPlaWnWOIh9CRJ1cIDB70pg_JD2l0nPTkPbtaTQaIGTz"; // 替换为实际的下载URL
+
+  try {
+    const response = await axios.post(
+      downloadUrl,
+      {
+        conversationId: "1976cfe3a5174f9ba768677f789cad7e",
+        content: "请输出纯文本的回答,不要markdown:如何学习英语?",
+        messageId: Date.now(),
+        applicationId: "",
+      },
+      {
+        headers: {
+          "Team-Id": "123456",
+          Authorization: "dffb9e92eec04c30bb441a5316a4e55c",
+          "Content-Type": "application/json",
+        },
+        onDownloadProgress: ({ event }) => {
+          //console.log(event);
+          const xhr = event.target;
+          const { responseText } = xhr;
+          //   // Always process the final line
+          //   const lastIndex = responseText.lastIndexOf(
+          //     "\n",
+          //     responseText.length - 2
+          //   );
+          //   let chunk = responseText;
+
+          //   if (lastIndex !== -1) chunk = responseText.substring(lastIndex);
+          //console.log(responseText, "chunk====");
+          text.value = responseText;
+          queue.add(async () => {
+            await splitMessage(responseText);
+          });
+        },
+      }
+    );
+    console.log(response);
+    console.log(messageQueue, "messageQueue=======0000");
+  } catch (error) {
+    console.error("下载文件时出错:", error);
+  }
+};
+onMounted(() => {});
+</script>

+ 57 - 0
src/views/demo/Tts.vue

@@ -0,0 +1,57 @@
+<template>
+  <div>
+    <div class="model-viewer" ref="modelViewer"></div>
+    <button @click="playTTS">播放语音</button>
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import axios from "axios";
+
+const modelViewer = ref(null);
+
+const playTTS = async () => {
+  const ttsMessage =
+    "替换为你想要播报的文本"; // 替换为你想要播报的文本
+
+  try {
+    const wgToken = `C1qziFGlIv3tnCQxcFaStrLuZOO2ZZXjN7FB_G0WlrOLjclfObbSaXAKzl4RWwQBf_0Zhsm0CoVvdVsYMD18iM_LJrxtn7LHJJQuF9UoUuF3fvqOwrG4EF6Z4GahtxtQ2oeaPQBBNKlgVW1xUW7tkhEdXWqzDHPA_I_91Lczk0PI4guhx1c88Hst4-HI8pdMbiUdEJzj3d3a2W06Fa0XA9Q0taAwaRd1k9jUrDVyj9GfS84_SIgJF4SPjWVfsraV79ieb_StgRcUwZjbscGPMlifnJD6F00wwNbxG7AuCHbl3EtMfSed1vuVx3AsizIckwzIVSVRpOGw72cdAMui-I6es9Ozj2ITzSa5KgyXEpX4qCHF1VcCM1wlHLQ_5hLnJIi4r8NsnJPsxMYrTw`;
+    const res = await axios.post(
+      `https://fls-ai-stg-sit.pingan.com.cn/openapi/ai/voice/tts/v2?channelId=ASP-TEST&sceneId=ASP-TEST&token=${wgToken}`,
+      { sessionId: "N7FB_G0WlrOLjc", text: ttsMessage },
+      {
+        responseType: "arraybuffer",
+        headers: {
+          "X-Ai-TTS-Appid": "2b1317fb5b284b308dc90a6fdeae6c4e",
+        },
+      }
+    );
+    console.log(res.data);
+
+    // 播放获取到的音频
+    playAudio(res.data);
+  } catch (error) {
+    console.error("Error calling TTS API:", error);
+  }
+};
+
+const playAudio = (audioData) => {
+  const blob = new Blob([audioData], { type: "audio/wav" });
+  const url = URL.createObjectURL(blob);
+  const audio = new Audio(url);
+  audio.onended = () => URL.revokeObjectURL(url);
+  audio.onerror = (error) => console.error("Audio playback error:", error);
+  audio
+    .play()
+    .then(() => console.log("Audio playing"))
+    .catch((error) => console.error("Error playing audio:", error));
+};
+</script>
+
+<style scoped>
+.model-viewer {
+  width: 100%;
+  height: 400px;
+}
+</style>

+ 92 - 0
src/views/result/randarPage.vue

@@ -0,0 +1,92 @@
+<script setup lang="ts">
+import { onMounted, defineProps, nextTick, watch ,ref} from 'vue'
+import * as echarts from 'echarts';
+const props = defineProps({
+    randarList: Array // 或者根据你的需求,使用其他类型
+});
+const list = ref([0,0,0,0])
+const echartsRander = (randarList) => {
+    const dom = document.getElementById('chartsId');
+    if (!dom) return;
+    const myChart = echarts.init(dom);
+
+    const option = {
+        color: ['#56A3F1'],
+        radar: {
+            center: ['51%', '50%'], // 设置雷达图的中心位置,避免坐标轴名称被遮挡
+            radius: '58%',
+            indicator: [
+                { name: '自我介绍专业性', max: 25 },
+                { name: '挖掘需求准确性', max: 25 },
+                { name: '清晰表达产品优势', max: 25 },
+                { name: '吸引客户兴趣', max: 25 }
+            ],
+            axisName: {
+                color: '#fff',
+                backgroundColor: '#666',
+                borderRadius: 3,
+                padding: [3, 3]
+            }
+        },
+        series: [
+            {
+                type: 'radar',
+                data: [
+                    {
+                        symbol: 'rect',
+                        symbolSize: 8,
+                        lineStyle: {
+                            type: 'dashed'
+                        },
+                        value: randarList,
+                        label: {
+                            show: true,
+                            formatter: function (params) {
+                                return params.value;
+                            }
+                        },
+                    }
+                ]
+            }
+        ]
+    };
+
+    option && myChart.setOption(option);
+    window.addEventListener('resize', function () {
+        myChart.resize();
+    });
+}
+
+// onMounted(() => {
+
+//     nextTick(()=>{
+//         console.log(props.randarList,'00000000000')
+//         echartsRander(props.randarList)
+//     })
+//     // echartsRander(props.randarList)
+// })
+
+onMounted(() => {
+    nextTick(() => {
+        if (props.randarList && props.randarList.length > 0) {
+            list.value = props.randarList;
+            echartsRander(props.randarList);
+        }
+    });
+});
+
+// 使用 watch 来观察 props.randarList 的变化
+watch(() => props.randarList, (newVal, oldVal) => {
+    if (newVal && newVal.length > 0) {
+        list.value = newVal
+        echartsRander(newVal);
+    }
+});
+
+</script>
+<template>
+    <!-- <div> -->
+        <div id="chartsId" style="width: 100%;height: 100%;"></div>
+        <!-- <div v-else>暂无数据</div> -->
+    <!-- </div> -->
+</template>

+ 133 - 0
src/views/result/selectPage.vue

@@ -0,0 +1,133 @@
+<template>
+    <div class="custom-select" ref="selectRef">
+        <!-- 触发区域 -->
+        <div class="custom-select-trigger" @click="toggleDropdown">
+            <span class="selected-text">{{ selectedOption?.text || placeholder }}</span>
+            <van-icon name="arrow-down" class="dropdown-icon" />
+        </div>
+
+        <!-- 下拉框内容 -->
+        <div v-if="showDropdown" class="custom-dropdown"
+            :style="{ top: dropdownStyle.top + 'px', left: dropdownStyle.left + 'px', width: dropdownStyle.width + 'px' }">
+            <div v-for="(option, index) in optionValue" :key="index" class="dropdown-item"
+                :class="{ 'dropdown-item-selected': option.value === selectedOption?.value }"
+                @click="selectOption(option)">
+                {{ option.text }}
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, nextTick,defineProps,defineEmits,onMounted,onBeforeUnmount} from 'vue';
+
+defineProps({
+    optionValue: {
+        type: Array,
+        required: true,
+    },
+    placeholder: {
+        type: String,
+        default: '请选择',
+    }
+})
+const emit = defineEmits(['update:selectedOption']);
+const showDropdown = ref(false);
+const selectedOption = ref(null);
+
+const selectRef = ref(null);
+const dropdownStyle = ref({});
+
+const toggleDropdown = async () => {
+    showDropdown.value = !showDropdown.value;
+    if (showDropdown.value) {
+        await nextTick();
+        updateDropdownPosition();
+    }
+};
+
+const selectOption = (option) => {
+    selectedOption.value = option;
+    emit('update:selectedOption', option);
+    showDropdown.value = false;
+};
+
+const updateDropdownPosition = () => {
+    const triggerRect = selectRef.value.getBoundingClientRect();
+    dropdownStyle.value = {
+        top: `${triggerRect.height}px`,
+        left: '0px',
+        width: `${triggerRect.width}px`,
+    };
+};
+
+const handleDocumentClick = (event) => {
+    if (!selectRef.value.contains(event.target)) {
+        showDropdown.value = false;
+    }
+};
+
+onMounted(() => {
+    document.addEventListener('click', handleDocumentClick);
+});
+
+onBeforeUnmount(() => {
+    document.removeEventListener('click', handleDocumentClick);
+});
+
+</script>
+
+<style scoped>
+.custom-select {
+    width: 11.56rem;
+    position: relative;
+}
+
+.custom-select-trigger {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 10px;
+    border: 1px solid #dcdfe6;
+    background: #fff;
+    border-radius: 0.38rem;
+    height: 2rem;
+}
+
+.custom-dropdown {
+    position: absolute;
+    z-index: 1000;
+    background: #fff;
+    border: 1px solid #ebeef5;
+    border-radius: 0.38rem;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+    max-height: 10rem;
+    overflow-y: auto;
+    width: 100%;
+    /* 保持与触发器等宽 */
+}
+
+.selected-text{
+    background-color: rgba(255,255,255,1);
+    color: rgba(16,16,16,1);
+    font-size: 0.88rem;
+    text-align: left;
+    font-family: PingFangSC-regular;
+}
+
+.dropdown-item {
+    padding: 5px 8px;
+    font-size: 0.88rem;
+    text-align: left;
+    cursor: pointer;
+    font-family: PingFangSC-regular;
+}
+
+.dropdown-item:hover {
+    background-color: #f5f7fa;
+}
+.dropdown-item-selected {
+    color: #fff;
+    background-color: #4095e5;
+}
+</style>

+ 18 - 0
src/views/utils/api.js

@@ -0,0 +1,18 @@
+import request from "./request";
+
+// 创建对话
+export async function fetchTaskCreate(data) {
+  return request(`/openapi-prd/ai/intelligent-tutoring/task/create`,{
+    method:"POST",
+    data,
+  });
+}
+
+//获取分数
+
+export async function fetchTaskScore(conversationId) {
+  return request(`/openapi-prd/ai/intelligent-tutoring/task/score?conversationId=${conversationId}`,{
+    method:"get",
+  });
+}
+

+ 46 - 0
src/views/utils/request.js

@@ -0,0 +1,46 @@
+import axios, { CanceledError } from 'axios'
+import { showToast } from 'vant'
+
+// 创建一个 axios 实例
+const service = axios.create({
+  baseURL: "", // 所有的请求地址前缀部分
+  timeout: 60000, // 请求超时时间毫秒
+  headers: {
+    // 设置后端需要的传参类型
+    'Content-Type': 'application/json'
+  }
+})
+
+// 添加请求拦截器
+service.interceptors.request.use(
+  (config) => {
+    return config
+  },
+  (error) => {
+    // 对请求错误做些什么
+    showToast('网络请求失败')
+    return Promise.reject(error)
+  }
+)
+
+// 添加响应拦截器
+service.interceptors.response.use(
+  (response) => {
+    const res = response.data
+    // 对响应错误做点什么
+    if (res.code !== 200) {
+        showToast(res.msg)
+    }
+    return res
+  },
+  (error) => {
+    // 对响应错误做点什么
+    if (error instanceof CanceledError) {
+      return Promise.reject(error)
+    }
+    showToast(error.response?.data?.message || error.message)
+    return Promise.reject(error)
+  }
+)
+
+export default service

+ 47 - 0
vite.config.js

@@ -0,0 +1,47 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import fs from 'fs'
+
+const stgToken = 'C1qziFGlIv3tnCQxcFaStrLuZOO2ZZXjN7FB_G0WlrOLjclfObbSaXAKzl4RWwQBf_0Zhsm0CoVvdVsYMD18iM_LJrxtn7LHJJQuF9UoUuF3fvqOwrG4EF6Z4GahtxtQ2oeaPQBBNKlgVW1xUW7tkhEdXWqzDHPA_I_91Lczk0PI4guhx1c88Hst4-HI8pdMbiUdEJzj3d3a2W06Fa0XA9Q0taAwaRd1k9jUrDVyj9GfS84_SIgJF4SPjWVfsraV79ieb_StgRcUwZjbscGPMlifnJD6F00wwNbxG7AuCHbl3EtMfSed1vuVx3AsizIckwzIVSVRpOGw72cdAMui-I6es9Ozj2ITzSa5KgyXEpX4qCHF1VcCM1wlHLQ_5hLnJIi4r8NsnJPsxMYrTw'
+
+const prdToken = 'mVcexzY_mjtGAL5_exPlmAyfOJxuuEthWY1mk9tUFC_HwceY58uRZ2WDhz7-ttexCdUtFN8C7V636_jIq6fzaSfqIj8OQyhUPKPMa2eZjLlblT77ySqBt_lYM6iEAhrj7-raGmySMmkLS4Rqh651Ak2tqmUbjS64cqv5ofMsuadOCg1J-CtLFt7NeSoU4N3Kpm5MJ_4sOFBhQGfBym88dcwxosFl9LbvhpyleXFf6fOZkkOj0l2X8Nr2pfNjYs3_VOmCQxrxXh1XZ_a1v9qj5_rA9k9wGNNQfmr2JwJTUT4V9NwtNq94gNFt8C0J6MWKVE2eyr25Rke8tkKu3CGNNmspmEFpr6LavPlaWnWOIh9CRJ1cIDB70pg_JD2l0nPTkPbtaTQaIGTz'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [vue()],
+  server: {
+    host: '0.0.0.0',
+    proxy: {
+      '/openapi-stg': {
+        target: 'https://fls-ai-stg-sit.pingan.com.cn/openapi', // 设置代理目标
+        changeOrigin: true,
+        rewrite: (path) => {
+          // 提取原始路径
+          const originalPath = path.replace(/^\/openapi-stg/, '');
+          // 判断是否包含查询参数,如果有则追加 &,否则追加 ?
+          const separator = originalPath.indexOf('?') === -1 ? '?' : '&';
+          // 拼接 token 参数
+          return `${originalPath}${separator}token=${stgToken}&channelId=ASP-TEST&sceneId=ASP-TEST`;
+        }
+      },
+      '/openapi-prd': {
+        target: 'https://fls-ai.pingan.com.cn/openapi', // 设置代理目标
+        changeOrigin: true,
+        //rewrite: (path) => path.replace(/^\/openapi-prd/, '') // 重写路径
+        rewrite: (path) => {
+          // 提取原始路径
+          const originalPath = path.replace(/^\/openapi-prd/, '');
+          // 判断是否包含查询参数,如果有则追加 &,否则追加 ?
+          const separator = originalPath.indexOf('?') === -1 ? '?' : '&';
+          // 拼接 token 参数
+          return `${originalPath}${separator}token=${prdToken}&channelId=ASP-TEST&sceneId=ASP-TEST`;
+        }
+      },
+      '/testTts': {
+        target: 'http://192.168.92.190:5001/', // 设置代理目标
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/testTts/, '') // 重写路径
+      }
+    }
+  }
+})