xueyiming 2 weeks ago
parent
commit
1583483fa5

+ 342 - 23
package-lock.json

@@ -8,12 +8,16 @@
       "name": "rag-web",
       "version": "0.1.0",
       "dependencies": {
+        "axios": "^1.12.0",
         "core-js": "^3.8.3",
+        "element-plus": "^2.11.2",
+        "marked": "^16.3.0",
         "vue": "^3.2.13",
         "vue-router": "^4.0.3",
         "vuex": "^4.0.0"
       },
       "devDependencies": {
+        "@types/lodash": "^4.17.20",
         "@typescript-eslint/eslint-plugin": "^5.4.0",
         "@typescript-eslint/parser": "^5.4.0",
         "@vue/cli-plugin-babel": "~5.0.0",
@@ -25,7 +29,7 @@
         "@vue/eslint-config-typescript": "^9.1.0",
         "eslint": "^7.32.0",
         "eslint-plugin-vue": "^8.0.3",
-        "typescript": "~4.5.5"
+        "typescript": "^5.9.2"
       }
     },
     "node_modules/@achrinza/node-ipc": {
@@ -1749,6 +1753,15 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/@discoveryjs/json-ext": {
       "version": "0.5.7",
       "resolved": "https://registry.npmmirror.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@@ -1759,6 +1772,15 @@
         "node": ">=10.0.0"
       }
     },
+    "node_modules/@element-plus/icons-vue": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
+      "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
+      "license": "MIT",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.9.0",
       "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
@@ -1819,6 +1841,31 @@
         "node": ">= 4"
       }
     },
+    "node_modules/@floating-ui/core": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+      "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.10"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.7.4",
+      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+      "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/core": "^1.7.3",
+        "@floating-ui/utils": "^0.2.10"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.10",
+      "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+      "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+      "license": "MIT"
+    },
     "node_modules/@hapi/hoek": {
       "version": "9.3.0",
       "resolved": "https://registry.npmmirror.com/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -1985,6 +2032,17 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.7",
+      "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
     "node_modules/@sideway/address": {
       "version": "4.1.5",
       "resolved": "https://registry.npmmirror.com/@sideway/address/-/address-4.1.5.tgz",
@@ -2186,6 +2244,21 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/lodash": {
+      "version": "4.17.20",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
+      "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
     "node_modules/@types/mime": {
       "version": "1.3.5",
       "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz",
@@ -2305,6 +2378,12 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.16",
+      "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+      "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
+      "license": "MIT"
+    },
     "node_modules/@types/webpack-env": {
       "version": "1.18.8",
       "resolved": "https://registry.npmmirror.com/@types/webpack-env/-/webpack-env-1.18.8.tgz",
@@ -3306,6 +3385,94 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@vueuse/core": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz",
+      "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.16",
+        "@vueuse/metadata": "9.13.0",
+        "@vueuse/shared": "9.13.0",
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz",
+      "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz",
+      "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+      "license": "MIT",
+      "dependencies": {
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@webassemblyjs/ast": {
       "version": "1.14.1",
       "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.14.1.tgz",
@@ -3778,6 +3945,18 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
+      "license": "MIT"
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
     "node_modules/at-least-node": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz",
@@ -3826,6 +4005,17 @@
         "postcss": "^8.1.0"
       }
     },
+    "node_modules/axios": {
+      "version": "1.12.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
+      "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.4",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
     "node_modules/babel-loader": {
       "version": "8.4.1",
       "resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-8.4.1.tgz",
@@ -4172,7 +4362,6 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
       "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "es-errors": "^1.3.0",
@@ -4507,6 +4696,18 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/commander": {
       "version": "8.3.0",
       "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz",
@@ -5084,6 +5285,12 @@
       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
       "license": "MIT"
     },
+    "node_modules/dayjs": {
+      "version": "1.11.18",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
+      "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
+      "license": "MIT"
+    },
     "node_modules/debounce": {
       "version": "1.2.1",
       "resolved": "https://registry.npmmirror.com/debounce/-/debounce-1.2.1.tgz",
@@ -5325,6 +5532,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/depd": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
@@ -5503,7 +5719,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
       "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "call-bind-apply-helpers": "^1.0.1",
@@ -5545,6 +5760,32 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/element-plus": {
+      "version": "2.11.2",
+      "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.11.2.tgz",
+      "integrity": "sha512-sTMDXtgeqy17TUsBSH/DL3h1/5sqIOVUUgXFoVbdD8lWWYssKrDX50CEYy4k29tYJhPHKZyFSwcLJsIajr+dDA==",
+      "license": "MIT",
+      "dependencies": {
+        "@ctrl/tinycolor": "^3.4.1",
+        "@element-plus/icons-vue": "^2.3.1",
+        "@floating-ui/dom": "^1.0.1",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+        "@types/lodash": "^4.14.182",
+        "@types/lodash-es": "^4.17.6",
+        "@vueuse/core": "^9.1.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.13",
+        "escape-html": "^1.0.3",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "lodash-unified": "^1.0.2",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
     "node_modules/emoji-regex": {
       "version": "8.0.0",
       "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -5660,7 +5901,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
       "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -5670,7 +5910,6 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
       "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -5687,7 +5926,6 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
       "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "es-errors": "^1.3.0"
@@ -5696,6 +5934,21 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/escalade": {
       "version": "3.2.0",
       "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
@@ -5710,7 +5963,6 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
       "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/escape-string-regexp": {
@@ -6639,7 +6891,6 @@
       "version": "1.15.11",
       "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
       "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
-      "dev": true,
       "funding": [
         {
           "type": "individual",
@@ -6745,6 +6996,22 @@
         "node": ">=10"
       }
     },
+    "node_modules/form-data": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+      "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz",
@@ -6828,7 +7095,6 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
       "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
-      "dev": true,
       "license": "MIT",
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -6865,7 +7131,6 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
       "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "call-bind-apply-helpers": "^1.0.2",
@@ -6890,7 +7155,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
       "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "dunder-proto": "^1.0.1",
@@ -7009,7 +7273,6 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
       "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -7082,7 +7345,6 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
       "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -7091,6 +7353,21 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/hash-sum": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz",
@@ -7102,7 +7379,6 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
       "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "function-bind": "^1.1.2"
@@ -8019,9 +8295,25 @@
       "version": "4.17.21",
       "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-      "dev": true,
       "license": "MIT"
     },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz",
+      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/lodash-es": "*",
+        "lodash": "*",
+        "lodash-es": "*"
+      }
+    },
     "node_modules/lodash.debounce": {
       "version": "4.0.8",
       "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -8283,11 +8575,22 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/marked": {
+      "version": "16.3.0",
+      "resolved": "https://registry.npmmirror.com/marked/-/marked-16.3.0.tgz",
+      "integrity": "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==",
+      "license": "MIT",
+      "bin": {
+        "marked": "bin/marked.js"
+      },
+      "engines": {
+        "node": ">= 20"
+      }
+    },
     "node_modules/math-intrinsics": {
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
       "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -8323,6 +8626,12 @@
         "node": ">= 4.0.0"
       }
     },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+      "license": "MIT"
+    },
     "node_modules/merge-descriptors": {
       "version": "1.0.3",
       "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
@@ -8401,7 +8710,6 @@
       "version": "1.52.0",
       "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.6"
@@ -8411,7 +8719,6 @@
       "version": "2.1.35",
       "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "mime-db": "1.52.0"
@@ -8783,6 +9090,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -10047,6 +10360,12 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "license": "MIT"
+    },
     "node_modules/pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz",
@@ -11654,17 +11973,17 @@
       }
     },
     "node_modules/typescript": {
-      "version": "4.5.5",
-      "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.5.5.tgz",
-      "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
-      "dev": true,
+      "version": "5.9.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
+      "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
+      "devOptional": true,
       "license": "Apache-2.0",
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
       },
       "engines": {
-        "node": ">=4.2.0"
+        "node": ">=14.17"
       }
     },
     "node_modules/undici-types": {

+ 5 - 1
package.json

@@ -8,12 +8,16 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "axios": "^1.12.0",
     "core-js": "^3.8.3",
+    "element-plus": "^2.11.2",
+    "marked": "^16.3.0",
     "vue": "^3.2.13",
     "vue-router": "^4.0.3",
     "vuex": "^4.0.0"
   },
   "devDependencies": {
+    "@types/lodash": "^4.17.20",
     "@typescript-eslint/eslint-plugin": "^5.4.0",
     "@typescript-eslint/parser": "^5.4.0",
     "@vue/cli-plugin-babel": "~5.0.0",
@@ -25,6 +29,6 @@
     "@vue/eslint-config-typescript": "^9.1.0",
     "eslint": "^7.32.0",
     "eslint-plugin-vue": "^8.0.3",
-    "typescript": "~4.5.5"
+    "typescript": "^5.9.2"
   }
 }

+ 15 - 23
src/App.vue

@@ -1,30 +1,22 @@
 <template>
-  <nav>
-    <router-link to="/">Home</router-link> |
-    <router-link to="/about">About</router-link>
-  </nav>
-  <router-view/>
+  <div id="app">
+    <AppNavbar/>
+    <router-view/>
+  </div>
 </template>
 
-<style>
-#app {
-  font-family: Avenir, Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-align: center;
-  color: #2c3e50;
-}
+<script lang="ts">
+import {defineComponent} from 'vue';
+import AppNavbar from './components/AppNavbar.vue';
 
-nav {
-  padding: 30px;
-}
+export default defineComponent({
+  name: 'App',
+  components: {
+    AppNavbar,
+  },
+});
+</script>
 
-nav a {
-  font-weight: bold;
-  color: #2c3e50;
-}
+<style scoped>
 
-nav a.router-link-exact-active {
-  color: #42b983;
-}
 </style>

+ 69 - 0
src/components/AppNavbar.vue

@@ -0,0 +1,69 @@
+<template>
+  <el-header>
+    <el-menu :default-active="activeRoute" mode="horizontal" @select="handleSelect">
+      <el-menu-item index="/">知识库</el-menu-item>
+      <el-menu-item index="/search">搜索</el-menu-item>
+      <el-menu-item index="/qanda">问答</el-menu-item>
+    </el-menu>
+  </el-header>
+</template>
+
+<script lang="ts">
+import { defineComponent, computed } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+
+export default defineComponent({
+  name: 'AppNavbar',
+  setup() {
+    const route = useRoute();
+    const router = useRouter();
+
+    // 动态计算激活的路由
+    const activeRoute = computed(() => {
+      // 这里可以根据实际情况,判断当前路径是否包含某些部分来保持激活项
+      if (route.path.startsWith('/knowledge/content')) {
+        return '/'; // 知识库页面时,设置为高亮 "知识库"
+      }
+      return route.path;
+    });
+
+
+    const handleSelect = (index: string) => {
+      router.push(index);
+    };
+
+    return {
+      activeRoute,
+      handleSelect,
+    };
+  },
+});
+</script>
+
+<style scoped>
+.el-header {
+  padding: 0;
+}
+
+.logo-container {
+  width: 50px;
+  height: 50px;
+  display: flex;
+  align-items: center;
+  padding: 10px 20px;
+}
+
+.el-menu {
+  font-size: 18px; /* 增大菜单字体 */
+  display: flex;
+  justify-content: center; /* 水平居中菜单 */
+  align-items: center; /* 垂直居中菜单 */
+  height: 100%;
+}
+
+.el-menu-item {
+  font-size: 18px;
+  text-align: center;
+  padding: 14px 20px;
+}
+</style>

+ 0 - 63
src/components/HelloWorld.vue

@@ -1,63 +0,0 @@
-<template>
-  <div class="hello">
-    <h1>{{ msg }}</h1>
-    <p>
-      For a guide and recipes on how to configure / customize this project,<br>
-      check out the
-      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
-    </p>
-    <h3>Installed CLI Plugins</h3>
-    <ul>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
-    </ul>
-    <h3>Essential Links</h3>
-    <ul>
-      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
-      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
-      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
-      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
-      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
-    </ul>
-    <h3>Ecosystem</h3>
-    <ul>
-      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
-      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
-      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
-      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
-      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
-    </ul>
-  </div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-  name: 'HelloWorld',
-  props: {
-    msg: String,
-  },
-});
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-h3 {
-  margin: 40px 0 0;
-}
-ul {
-  list-style-type: none;
-  padding: 0;
-}
-li {
-  display: inline-block;
-  margin: 0 10px;
-}
-a {
-  color: #42b983;
-}
-</style>

+ 8 - 1
src/main.ts

@@ -3,4 +3,11 @@ import App from './App.vue'
 import router from './router'
 import store from './store'
 
-createApp(App).use(store).use(router).mount('#app')
+import ElementPlus from 'element-plus';
+import 'element-plus/dist/index.css'; // 引入 Element Plus 样式
+
+createApp(App)
+    .use(store)
+    .use(router)
+    .use(ElementPlus)
+    .mount('#app')

+ 22 - 10
src/router/index.ts

@@ -1,20 +1,32 @@
 import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
-import HomeView from '../views/HomeView.vue'
+import KnowledgeBase from "@/views/KnowledgeBase.vue";
+import KnowledgeContent from "@/views/KnowledgeContent.vue";
+import SearchPage from "@/views/SearchPage.vue";
+import QAndA from "@/views/QAndA.vue";
+
+
 
 const routes: Array<RouteRecordRaw> = [
   {
     path: '/',
-    name: 'home',
-    component: HomeView
+    name: 'KnowledgeBase',
+    component: KnowledgeBase,
+  },
+  {
+    path: '/knowledge/content',
+    name: 'KnowledgeContent',
+    component: KnowledgeContent,
   },
   {
-    path: '/about',
-    name: 'about',
-    // route level code-splitting
-    // this generates a separate chunk (about.[hash].js) for this route
-    // which is lazy-loaded when the route is visited.
-    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
-  }
+    path: '/search',
+    name: 'SearchPage',
+    component: SearchPage,
+  },
+  {
+    path: '/qanda',
+    name: 'QAndA',
+    component: QAndA,
+  },
 ]
 
 const router = createRouter({

+ 0 - 5
src/views/AboutView.vue

@@ -1,5 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>

+ 0 - 18
src/views/HomeView.vue

@@ -1,18 +0,0 @@
-<template>
-  <div class="home">
-    <img alt="Vue logo" src="../assets/logo.png">
-    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
-  </div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
-
-export default defineComponent({
-  name: 'HomeView',
-  components: {
-    HelloWorld,
-  },
-});
-</script>

+ 180 - 0
src/views/KnowledgeBase.vue

@@ -0,0 +1,180 @@
+<template>
+  <el-container style="height: 100vh; padding: 20px;">
+    <!-- Header: Knowledge Base title + Create button -->
+    <el-header style="display: flex; justify-content: space-between; align-items: center; padding: 20px;">
+      <div style="font-size: 28px; font-weight: bold; color: #333;">知识库</div>
+      <el-button type="primary" @click="showCreateModal" style="border-radius: 20px;">创建知识库</el-button>
+    </el-header>
+
+    <!-- Content Area: Display Knowledge Base List -->
+    <el-main style="padding: 20px;">
+      <div class="knowledge-base-grid">
+        <el-card
+            v-for="item in knowledgeBaseList"
+            :key="item.dataset_id"
+            :body-style="{ padding: '20px', borderRadius: '12px' }"
+            class="box-card"
+            @click="goToDetailPage(item.dataset_id, item.name)"
+        >
+          <div style="font-size: 18px; font-weight: bold; margin-bottom: 10px; color: #2c3e50;">
+            {{ item.name }}
+          </div>
+          <div style="color: #7f8c8d;">文档数量:{{ item.count }}</div>
+          <div style="color: #7f8c8d;">创建时间:{{ item.created_at }}</div>
+        </el-card>
+      </div>
+    </el-main>
+
+    <!-- Modal for Creating Knowledge Base -->
+    <el-dialog v-model="createModalVisible" title="创建知识库" width="400px" style="border-radius: 12px;">
+      <el-input
+          v-model="newKnowledgeBaseTitle"
+          placeholder="请输入知识库名称"
+          maxlength="64"
+          style="border-radius: 10px; padding: 10px;"
+      />
+      <!-- 显示已输入字符数/最大字符数 -->
+      <div style="text-align: right; margin-top: 5px; font-size: 12px; color: #999;">
+        {{ newKnowledgeBaseTitle.length }}/64
+      </div>
+      <template #footer>
+        <el-button @click="createModalVisible = false" style="border-radius: 20px;">取消</el-button>
+        <el-button
+            type="primary"
+            @click="createKnowledgeBase"
+            style="border-radius: 20px; background-color: #409EFF; border-color: #409EFF;">
+          创建
+        </el-button>
+      </template>
+    </el-dialog>
+  </el-container>
+</template>
+
+<script lang="ts">
+import {defineComponent, ref} from 'vue';
+import axios from 'axios';
+import {ElMessage} from 'element-plus';
+import {useRouter} from 'vue-router'; // 导入 useRouter
+
+interface KnowledgeBaseItem {
+  dataset_id: number;
+  name: string;
+  count: number;
+  create_at: string;
+}
+
+export default defineComponent({
+  name: 'KnowledgeBase',
+  setup() {
+    const router = useRouter(); // 获取 router 实例
+    const knowledgeBaseList = ref<KnowledgeBaseItem[]>([]);
+    const newKnowledgeBaseTitle = ref('');
+    const createModalVisible = ref(false);
+
+    // 获取知识库列表
+    const fetchKnowledgeBaseList = async () => {
+      try {
+        const response = await axios.get('http://192.168.245.124:5000/dataset/list');
+        knowledgeBaseList.value = response.data.data;
+      } catch (error) {
+        ElMessage.error('获取知识库列表失败');
+      }
+    };
+
+    // 显示创建知识库的弹窗
+    const showCreateModal = () => {
+      createModalVisible.value = true;
+    };
+
+    // 创建新的知识库
+    const createKnowledgeBase = async () => {
+      if (!newKnowledgeBaseTitle.value.trim()) {
+        ElMessage.warning('请输入知识库名称');
+        return;
+      }
+
+      try {
+        await axios.post('http://192.168.245.124:5000/dataset/add', {
+          name: newKnowledgeBaseTitle.value
+        });
+        ElMessage.success('知识库创建成功');
+        createModalVisible.value = false;
+        newKnowledgeBaseTitle.value = ''; // 清空输入框
+        fetchKnowledgeBaseList(); // 重新获取知识库列表
+      } catch (error) {
+        ElMessage.error('创建知识库失败');
+      }
+    };
+
+    // 跳转到知识库详情页面
+    const goToDetailPage = (datasetId: number, datasetName: string) => {
+      console.log(`跳转到知识库 ${datasetId} 的详情页`);
+      router.push({path: `/knowledge/content`, query: {datasetId: datasetId.toString(), datasetName: datasetName}});
+    };
+
+    // 初始化加载知识库列表
+    fetchKnowledgeBaseList();
+
+    return {
+      knowledgeBaseList,
+      newKnowledgeBaseTitle,
+      createModalVisible,
+      showCreateModal,
+      createKnowledgeBase,
+      goToDetailPage,
+    };
+  }
+});
+</script>
+
+<style scoped>
+.el-card {
+  cursor: pointer;
+  transition: transform 0.3s ease;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
+  border-radius: 12px; /* 添加圆角 */
+  background-color: #fff;
+  width: 250px; /* 设置卡片的固定宽度 */
+}
+
+.el-card:hover {
+  transform: translateY(-10px); /* 鼠标悬停时,卡片上升 */
+}
+
+.el-dialog {
+  z-index: 9999;
+  border-radius: 12px; /* 弹窗圆角 */
+}
+
+.el-button {
+  border-radius: 20px; /* 按钮圆角 */
+}
+
+.el-input {
+  border-radius: 10px; /* 输入框圆角 */
+}
+
+.knowledge-base-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px; /* 设置每个卡片之间的间距 */
+  justify-content: flex-start; /* 默认从左侧对齐 */
+}
+
+.el-card {
+  margin-top: 20px; /* 设置上边距为20px */
+}
+
+/* 适配小屏幕 */
+@media (max-width: 768px) {
+  .el-card {
+    width: 230px; /* 在小屏幕下调整卡片宽度 */
+  }
+}
+
+@media (max-width: 480px) {
+  .el-card {
+    width: 100%; /* 屏幕宽度小于480px时,每行展示1个卡片 */
+  }
+}
+</style>

+ 277 - 0
src/views/KnowledgeContent.vue

@@ -0,0 +1,277 @@
+<template>
+  <el-container style="height: 100vh;">
+    <!-- 上方的 Header -->
+    <el-header style="height: 50px; background: linear-gradient(to right, #6db3f2, #1e69e8); color: white; display: flex; align-items: center; padding: 0 20px;">
+      <!-- 返回按钮 -->
+      <el-button @click="goBack" style="margin-right: 20px; background-color: #fff; color: #409EFF; border: 1px solid #409EFF;">
+        返回
+      </el-button>
+
+      <!-- 知识库名称 -->
+      <div style="font-size: 18px; font-weight: bold; flex-grow: 1;">
+        {{ datasetName }}
+      </div>
+
+      <!-- 添加新知识按钮 -->
+      <el-button @click="openDialog" style="border-radius: 20px; background-color: #fff; color: #409EFF; border: 1px solid #409EFF;">
+        添加新知识
+      </el-button>
+    </el-header>
+
+    <el-container>
+      <!-- 左侧菜单栏 -->
+      <el-aside width="330px" style="background-color: #f4f4f4; padding: 10px; display: flex; flex-direction: column; overflow-y: auto; height: calc(100vh - 60px);">
+        <!-- 左侧标题,居中显示 -->
+        <div style="font-size: 18px; font-weight: bold; margin-bottom: 10px; text-align: center; width: 100%;">
+          内容列表
+        </div>
+
+        <el-menu :default-active="activeItem" @select="handleSelect" style="border-right: none; flex-grow: 1;">
+          <el-menu-item v-for="item in currentPageItems" :key="item.doc_id" :index="item.doc_id">
+            {{ item.text.substring(0, 20) + '...' }}
+          </el-menu-item>
+        </el-menu>
+
+        <!-- 分页 -->
+        <el-pagination
+            v-model:current-page="pageIndex"
+            :page-size="pageSize"
+            :total="totalItems"
+            :pager-count="5"
+            layout="prev, pager, next"
+            @current-change="handlePageChange"
+            style="margin-top: 20px; text-align: center;"
+        />
+      </el-aside>
+
+      <!-- 右侧内容区域 -->
+      <el-main style="padding: 20px; overflow-y: auto; height: calc(100vh - 60px);">
+        <h2>{{ selectedTitle }}</h2>
+        <p>{{ selectedContent }}</p>
+      </el-main>
+    </el-container>
+
+    <!-- 弹窗 -->
+    <el-dialog title="添加新知识" v-model="dialogVisible" @close="resetForm">
+      <el-form :model="formData" ref="formRef" label-width="80px">
+        <el-form-item label="标题" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
+          <el-input v-model="formData.title" placeholder="请输入标题"></el-input>
+        </el-form-item>
+
+        <el-form-item label="文本" :rules="[{ required: true, message: '请输入文本内容', trigger: 'blur' }]">
+          <el-input type="textarea" v-model="formData.text" placeholder="请输入文本" rows="6"></el-input>
+        </el-form-item>
+      </el-form>
+
+      <!-- 使用 <template> 标签来声明插槽 -->
+      <template #footer>
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitData">确 定</el-button>
+      </template>
+    </el-dialog>
+  </el-container>
+</template>
+
+<script lang="ts">
+import { defineComponent, onMounted, ref, computed } from 'vue';
+import { useRouter } from 'vue-router'; // 引入 vue-router 用于路由跳转
+import axios from 'axios';
+import { ElMessage } from 'element-plus'; // 导入 ElMessage
+
+
+// 定义接口类型
+interface Item {
+  doc_id: string;
+  title: string | null;
+  text: string;
+}
+
+export default defineComponent({
+  name: 'KnowledgeContent',
+  setup() {
+    const router = useRouter();
+    const datasetId = ref<number | null>(null);
+    const datasetName = ref<string | null>('');
+    // 当前选中的菜单项
+    const activeItem = ref('');
+    // 当前选中的内容的标题和内容
+    const selectedTitle = ref('');
+    const selectedContent = ref('');
+
+    // 页码控制
+    const pageIndex = ref(1);
+    const pageSize = ref(10);
+    const totalItems = ref(0);
+
+    // 使用 Item[] 来明确 allItems 数组的类型
+    const allItems = ref<Item[]>([]);
+
+    // 新知识表单数据
+    const formData = ref({
+      title: '',
+      text: '',
+    });
+
+    const dialogVisible = ref(false); // 控制弹窗的显示和隐藏
+
+    // 获取API数据并填充项
+    const fetchData = async (datasetId: number) => {
+      console.log(datasetId);
+      try {
+        const response = await axios.get('http://192.168.245.124:5000/content/list', {
+          params: {
+            page: pageIndex.value,
+            pageSize: pageSize.value,
+            datasetId: datasetId, // 可以传递 datasetId 作为查询参数
+          }
+        });
+        const data = response.data.data;
+        allItems.value = data.entities;
+        totalItems.value = data.total_count;
+        console.log(data);  // 输出数据查看
+      } catch (error) {
+        console.error('Error fetching data:', error);
+      }
+    };
+
+    // 当前页面的项目
+    const currentPageItems = computed(() => {
+      return allItems.value; // 不再切片,由后端控制分页
+    });
+
+    // 处理菜单项选择
+    const handleSelect = (doc_id: string) => {
+      activeItem.value = doc_id;
+      const selected = allItems.value.find(item => item.doc_id === doc_id);
+      if (selected) {
+        selectedTitle.value = selected.title || ''; // 如果 title 为 null,使用默认值
+        selectedContent.value = selected.text;
+      }
+    };
+
+    // 处理页码变更
+    const handlePageChange = (newPage: number) => {
+      pageIndex.value = newPage;
+      if (datasetId.value !== null) { // 确保 datasetId 不是 null
+        fetchData(datasetId.value);  // 重新获取数据
+      } else {
+        console.error('datasetId is null');
+      }
+    };
+
+    // 初始化数据
+    onMounted(() => {
+      const query = new URLSearchParams(window.location.search);
+      datasetId.value = query.get('datasetId') ? parseInt(query.get('datasetId')!) : null;
+      datasetName.value = query.get('datasetName')
+
+      // 只有在 datasetId 存在时才调用 fetchData
+      if (datasetId.value !== null) {
+        fetchData(datasetId.value);
+      } else {
+        console.error('datasetId is null');
+      }
+
+      console.log(`知识库 ID: ${datasetId.value}`);
+    });
+
+    // 返回首页路由
+    const goBack = () => {
+      router.push('/');  // 跳转到首页
+    };
+
+    // 打开弹窗
+    const openDialog = () => {
+      dialogVisible.value = true;
+    };
+
+    // 重置表单
+    const resetForm = () => {
+      formData.value.title = '';
+      formData.value.text = '';
+    };
+
+    // 提交表单数据
+    const submitData = async () => {
+      if (!formData.value.title || !formData.value.text) {
+        // 如果标题或文本为空,提示用户
+        ElMessage.error('标题和文本不能为空');
+        return;
+      }
+
+      try {
+        const response = await axios.post('http://192.168.245.124:5000/content/add', {
+          datasetId: datasetId.value,
+          title: formData.value.title,
+          text: formData.value.text,
+        });
+
+        console.log('提交成功:', response.data);
+        // 提交成功后隐藏弹窗
+        dialogVisible.value = false;
+        // 重新加载数据
+        if (datasetId.value !== null) { // 确保 datasetId 不是 null
+          fetchData(datasetId.value);  // 重新获取数据
+        } else {
+          console.error('datasetId is null');
+        }
+
+        // 显示提交成功的提示
+        ElMessage.success('新知识添加成功!');
+
+      } catch (error) {
+        console.error('提交失败:', error);
+        // 提交失败时给用户提示
+        ElMessage.error('提交失败,请稍后再试!');
+      }
+    };
+
+    return {
+      activeItem,
+      selectedTitle,
+      selectedContent,
+      pageIndex,
+      pageSize,
+      totalItems,
+      currentPageItems,
+      handleSelect,
+      handlePageChange,
+      datasetName,
+      goBack,
+      formData,
+      dialogVisible,
+      openDialog,
+      submitData,
+      resetForm,
+    };
+  },
+});
+</script>
+
+<style scoped>
+.el-container {
+  display: flex;
+  height: calc(100vh - 60px); /* 减去导航栏的高度 */
+}
+
+.el-aside {
+  background-color: #f4f4f4;
+  padding: 10px;
+  overflow-y: auto; /* 允许纵向滚动 */
+}
+
+.el-main {
+  background-color: #fff;
+  padding: 20px;
+  overflow-y: auto; /* 允许纵向滚动 */
+}
+
+.el-menu-item {
+  padding: 10px 20px;
+  font-size: 14px; /* 控制菜单项的字体大小 */
+  height: auto;
+  white-space: nowrap; /* 不换行 */
+  overflow: hidden; /* 超出部分隐藏 */
+  text-overflow: ellipsis; /* 使用省略号代替超出的文本 */
+}
+</style>

+ 295 - 0
src/views/QAndA.vue

@@ -0,0 +1,295 @@
+<template>
+  <div class="container">
+    <!-- 左侧知识库列表 -->
+    <div class="knowledge-base">
+      <h3>选择知识库</h3>
+      <div
+          v-for="item in knowledgeBaseList"
+          :key="item.dataset_id"
+          :class="['knowledge-item', { 'active': selectedDatasetIds.includes(item.dataset_id) }]"
+          @click="toggleDatasetSelection(item.dataset_id)"
+      >
+        {{ item.name }}
+      </div>
+    </div>
+
+    <!-- 右侧搜索框和搜索结果 -->
+    <div class="search-area">
+      <div class="search-container">
+        <el-input
+            v-model="query"
+            placeholder="请输入提问内容"
+            suffix-icon="el-icon-search"
+            @keyup.enter="chat"
+            class="search-input"
+        ></el-input>
+        <el-button @click="chat" type="primary" style="margin-left: 15px">提问</el-button>
+      </div>
+
+      <!-- 搜索中的提示 -->
+      <div v-if="loading" class="loading-spinner">回答中...</div>
+      <!-- 展示 chat_res 数据 -->
+      <div v-if="chatSummary" class="chat-summary">
+        <h4>回答内容</h4>
+        <div v-html="parsedChatSummary"></div> <!-- 这里将解析后的内容渲染到页面 -->
+      </div>
+      <div class="search-results">
+        <!-- 只有当 searchResults 不为空时才显示外层的卡片 -->
+        <el-card v-if="searchResults.length > 0" class="search-card">
+          <h4>搜索内容</h4> <!-- 新增标题 "搜索内容" -->
+
+          <!-- 现有的搜索结果卡片循环 -->
+          <div v-for="result in searchResults" :key="result.contentSummary">
+            <el-card
+                class="result-card"
+                @click="handleDetails(result)"
+            >
+              <h3>{{ result.contentSummary }}</h3>
+              <p>{{ result.content.substring(0, 100) }}...</p>
+              <div class="meta">
+                <span>相似度: {{ result.score.toFixed(2) }}</span>
+                <span>知识库: {{ result.datasetName }}</span>
+              </div>
+            </el-card>
+          </div>
+        </el-card>
+      </div>
+    </div>
+  </div>
+
+  <!-- 弹窗:展示完整内容 -->
+  <el-dialog v-model="dialogVisible" width="80%">
+    <div>
+      <h3>{{ selectedResult.contentSummary }}</h3>
+      <p>{{ selectedResult.content }}</p>
+      <hr />
+      <div>
+        <h4>原文内容:</h4>
+        <p>{{ originalContent }}</p>
+      </div>
+    </div>
+    <template #footer>
+      <el-button @click="dialogVisible = false">关闭</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, onMounted, computed } from 'vue';
+import { ElMessage } from 'element-plus';
+import { marked } from 'marked';  // 使用命名导入
+
+// 存储选择的知识库数据
+const knowledgeBaseList = ref([]);
+const selectedDatasetIds = ref([]);
+
+// 搜索框输入内容
+const query = ref('');
+
+// 存储搜索结果
+const searchResults = ref([]);
+
+// 存储chat_res总结
+const chatSummary = ref('');
+
+// 弹窗显示状态
+const dialogVisible = ref(false);
+
+// 存储选中的搜索结果
+const selectedResult = ref({});
+
+// 存储原文内容
+const originalContent = ref('');
+
+// 搜索加载状态
+const loading = ref(false);
+
+// 请求知识库列表
+const getKnowledgeBaseList = async () => {
+  try {
+    const response = await fetch('http://192.168.245.124:5000/dataset/list');
+    const data = await response.json();
+    knowledgeBaseList.value = data.data;
+  } catch (error) {
+    ElMessage.error('获取知识库列表失败');
+  }
+};
+
+// 选择或取消选择知识库
+const toggleDatasetSelection = (datasetId) => {
+  const index = selectedDatasetIds.value.indexOf(datasetId);
+  if (index === -1) {
+    selectedDatasetIds.value.push(datasetId);
+  } else {
+    selectedDatasetIds.value.splice(index, 1);
+  }
+};
+
+// 执行搜索操作
+const chat = async () => {
+  if (!query.value.trim()) {
+    ElMessage.warning('请输入提问内容');
+    return;
+  }
+
+  if (selectedDatasetIds.value.length === 0) {
+    ElMessage.warning('请先选择知识库');
+    return;
+  }
+
+  loading.value = true; // 开始搜索时显示加载提示
+  const datasetIds = selectedDatasetIds.value.join(',');
+  try {
+    const response = await fetch(`http://192.168.245.124:5000/chat?query=${query.value}&datasetIds=${datasetIds}`);
+    const data = await response.json();
+    searchResults.value = data.data.results.map((item) => ({
+      ...item,
+    }));
+
+    // 获取并设置 chat_res
+    if (data.data.chat_res) {
+      chatSummary.value = data.data.chat_res;
+    }
+
+  } catch (error) {
+    ElMessage.error('搜索失败');
+  } finally {
+    loading.value = false; // 搜索结束后隐藏加载提示
+  }
+};
+
+// 展示选中的搜索结果的完整内容
+const handleDetails = async (result) => {
+  selectedResult.value = result;
+  dialogVisible.value = true; // 打开弹窗
+
+  // 请求完整内容
+  try {
+    const response = await fetch(`http://192.168.245.124:5000/content/get?docId=${result.docId}`);
+    const data = await response.json();
+    if (data.status_code === 200) {
+      originalContent.value = data.data.text; // 显示原文内容
+    } else {
+      ElMessage.error('获取原文内容失败');
+    }
+  } catch (error) {
+    ElMessage.error('请求原文内容失败');
+  }
+};
+
+// 计算属性:解析 chatSummary(如果是 Markdown 则解析)
+const parsedChatSummary = computed(() => {
+  if (chatSummary.value) {
+    // 分开检查 Markdown 格式的常见符号
+    const markdownSymbols = /[#*+\-`>!]/;  // 检测 Markdown 中常见的特殊字符
+    const isMarkdown = markdownSymbols.test(chatSummary.value);
+
+    // 如果检测到 Markdown 符号,解析为 Markdown 格式
+    if (isMarkdown) {
+      return marked(chatSummary.value);  // 使用 marked 库解析 Markdown
+    }
+  }
+  return chatSummary.value;  // 如果不是 Markdown 格式,直接返回文本
+});
+
+
+// 页面初始化加载知识库列表
+onMounted(() => {
+  getKnowledgeBaseList();
+});
+</script>
+
+<style scoped>
+.container {
+  display: flex;
+  justify-content: space-between;
+  padding: 20px;
+  height: 100vh; /* 设置容器高度为视口高度 */
+}
+
+.knowledge-base {
+  width: 20%;
+  background-color: #f9f9f9;
+  padding: 15px;
+  border-radius: 8px;
+  height: 100%; /* 确保高度为100% */
+  overflow-y: auto; /* 启用垂直滚动 */
+}
+
+.knowledge-item {
+  padding: 10px;
+  border-radius: 5px;
+  cursor: pointer;
+  margin: 5px 0;
+  transition: background-color 0.3s ease;
+}
+
+.knowledge-item:hover {
+  background-color: #e6f7ff;
+}
+
+.knowledge-item.active {
+  background-color: #b3d8ff;
+}
+
+.search-area {
+  width: 75%;
+  background-color: #ffffff;
+  padding: 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  height: 100%; /* 确保高度为100% */
+  overflow-y: auto; /* 启用垂直滚动 */
+}
+
+.search-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.search-input {
+  width: 60%; /* 控制搜索框的宽度 */
+}
+
+.result-card {
+  margin-top: 10px;
+  cursor: pointer;
+}
+
+.result-card:hover {
+  background-color: #f5f5f5;
+}
+
+.meta {
+  display: flex;
+  justify-content: space-between;
+  font-size: 12px;
+  color: #888;
+}
+
+.loading-spinner {
+  text-align: center;
+  font-size: 16px;
+  color: #888;
+  margin-top: 20px;
+}
+
+.chat-summary {
+  background-color: #f0f8ff;
+  padding: 15px;
+  border-radius: 8px;
+  margin-bottom: 20px;
+}
+
+.chat-summary h4 {
+  font-size: 18px;
+  font-weight: bold;
+}
+
+.chat-summary p {
+  font-size: 14px;
+  line-height: 1.5;
+}
+</style>

+ 242 - 0
src/views/SearchPage.vue

@@ -0,0 +1,242 @@
+<template>
+  <div class="container">
+    <!-- 左侧知识库列表 -->
+    <div class="knowledge-base">
+      <h3>选择知识库</h3>
+      <div
+          v-for="item in knowledgeBaseList"
+          :key="item.dataset_id"
+          :class="['knowledge-item', { 'active': selectedDatasetIds.includes(item.dataset_id) }]"
+          @click="toggleDatasetSelection(item.dataset_id)"
+      >
+        {{ item.name }}
+      </div>
+    </div>
+
+    <!-- 右侧搜索框和搜索结果 -->
+    <div class="search-area">
+      <div class="search-container">
+        <el-input
+            v-model="query"
+            placeholder="请输入搜索内容"
+            suffix-icon="el-icon-search"
+            @keyup.enter="search"
+            class="search-input"
+        ></el-input>
+        <el-button @click="search" type="primary" style="margin-left: 15px">搜索</el-button>
+      </div>
+      <!-- 搜索中的提示 -->
+      <div v-if="loading" class="loading-spinner">搜索中...</div>
+      <div class="search-results">
+        <el-card
+            v-for="result in searchResults"
+            :key="result.contentSummary"
+            class="result-card"
+            @click="handleDetails(result)"
+        >
+          <h3>{{ result.contentSummary }}</h3>
+          <p>{{ result.content.substring(0, 100) }}...</p>
+          <div class="meta">
+            <span>相似度: {{ result.score.toFixed(2) }}</span>
+            <span>知识库: {{ result.datasetName }}</span>
+          </div>
+        </el-card>
+      </div>
+    </div>
+  </div>
+
+  <!-- 弹窗:展示完整内容 -->
+  <el-dialog v-model="dialogVisible" width="80%">
+    <div>
+      <h3>{{ selectedResult.contentSummary }}</h3>
+      <p>{{ selectedResult.content }}</p>
+      <hr />
+      <div>
+        <h4>原文内容:</h4>
+        <p>{{ originalContent }}</p>
+      </div>
+    </div>
+    <template #footer>
+      <el-button @click="dialogVisible = false">关闭</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import { ElMessage } from 'element-plus';
+
+// 存储选择的知识库数据
+const knowledgeBaseList = ref([]);
+const selectedDatasetIds = ref([]);
+
+// 搜索框输入内容
+const query = ref('');
+
+// 存储搜索结果
+const searchResults = ref([]);
+
+// 弹窗显示状态
+const dialogVisible = ref(false);
+
+// 存储选中的搜索结果
+const selectedResult = ref({});
+
+// 存储原文内容
+const originalContent = ref('');
+
+// 搜索加载状态
+const loading = ref(false);
+
+// 请求知识库列表
+const getKnowledgeBaseList = async () => {
+  try {
+    const response = await fetch('http://192.168.245.124:5000/dataset/list');
+    const data = await response.json();
+    knowledgeBaseList.value = data.data;
+  } catch (error) {
+    ElMessage.error('获取知识库列表失败');
+  }
+};
+
+// 选择或取消选择知识库
+const toggleDatasetSelection = (datasetId) => {
+  const index = selectedDatasetIds.value.indexOf(datasetId);
+  if (index === -1) {
+    selectedDatasetIds.value.push(datasetId);
+  } else {
+    selectedDatasetIds.value.splice(index, 1);
+  }
+};
+
+// 执行搜索操作
+const search = async () => {
+  if (!query.value.trim()) {
+    ElMessage.warning('请输入搜索内容');
+    return;
+  }
+
+  if (selectedDatasetIds.value.length === 0) {
+    ElMessage.warning('请先选择知识库');
+    return;
+  }
+
+  loading.value = true; // 开始搜索时显示加载提示
+  const datasetIds = selectedDatasetIds.value.join(',');
+  try {
+    const response = await fetch(`http://192.168.245.124:5000/query?query=${query.value}&datasetIds=${datasetIds}`);
+    const data = await response.json();
+    searchResults.value = data.data.results.map((item) => ({
+      ...item,
+      // 这里只是简化了展示,可以将 datasetName 从返回结果中提取
+    }));
+  } catch (error) {
+    ElMessage.error('搜索失败');
+  } finally {
+    loading.value = false; // 搜索结束后隐藏加载提示
+  }
+};
+
+// 展示选中的搜索结果的完整内容
+const handleDetails = async (result) => {
+  selectedResult.value = result;
+  dialogVisible.value = true; // 打开弹窗
+
+  // 请求完整内容
+  try {
+    const response = await fetch(`http://192.168.245.124:5000/content/get?docId=${result.docId}`);
+    const data = await response.json();
+    if (data.status_code === 200) {
+      originalContent.value = data.data.text; // 显示原文内容
+    } else {
+      ElMessage.error('获取原文内容失败');
+    }
+  } catch (error) {
+    ElMessage.error('请求原文内容失败');
+  }
+};
+
+// 页面初始化加载知识库列表
+onMounted(() => {
+  getKnowledgeBaseList();
+});
+</script>
+
+<style scoped>
+.container {
+  display: flex;
+  justify-content: space-between;
+  padding: 20px;
+  height: 100vh; /* 设置容器高度为视口高度 */
+}
+
+.knowledge-base {
+  width: 20%;
+  background-color: #f9f9f9;
+  padding: 15px;
+  border-radius: 8px;
+  height: 100%; /* 确保高度为100% */
+  overflow-y: auto; /* 启用垂直滚动 */
+}
+
+.knowledge-item {
+  padding: 10px;
+  border-radius: 5px;
+  cursor: pointer;
+  margin: 5px 0;
+  transition: background-color 0.3s ease;
+}
+
+.knowledge-item:hover {
+  background-color: #e6f7ff;
+}
+
+.knowledge-item.active {
+  background-color: #b3d8ff;
+}
+
+.search-area {
+  width: 75%;
+  background-color: #ffffff;
+  padding: 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  height: 100%; /* 确保高度为100% */
+  overflow-y: auto; /* 启用垂直滚动 */
+}
+
+.search-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.search-input {
+  width: 60%; /* 控制搜索框的宽度 */
+}
+
+.result-card {
+  margin-top: 10px;
+  cursor: pointer;
+}
+
+.result-card:hover {
+  background-color: #f5f5f5;
+}
+
+.meta {
+  display: flex;
+  justify-content: space-between;
+  font-size: 12px;
+  color: #888;
+}
+
+.loading-spinner {
+  text-align: center;
+  font-size: 16px;
+  color: #888;
+  margin-top: 20px;
+}
+
+</style>