Переглянути джерело

Merge branch 'web3_transection'

Hexinkui 3 тижнів тому
батько
коміт
b824cb7d3b

+ 494 - 36
package-lock.json

@@ -15,7 +15,7 @@
         "postcss-pxtorem": "^6.1.0",
         "vant": "^4.9.21",
         "vue": "^3.2.13",
-        "vue-i18n": "^12.0.0-alpha.3",
+        "vue-i18n": "^9.14.5",
         "vue-router": "^4.0.3",
         "vuex": "^4.0.0"
       },
@@ -27,7 +27,9 @@
         "babel-plugin-import": "^1.13.8",
         "less": "^4.0.0",
         "less-loader": "^8.0.0",
-        "postcss-pxtorem": "^6.1.0"
+        "postcss-pxtorem": "^6.1.0",
+        "sass": "^1.94.2",
+        "sass-loader": "^10.5.2"
       }
     },
     "node_modules/@achrinza/node-ipc": {
@@ -234,12 +236,13 @@
       }
     },
     "node_modules/@intlify/core-base": {
-      "version": "12.0.0-alpha.3",
-      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-12.0.0-alpha.3.tgz",
-      "integrity": "sha512-LEvBHBUbiOOtIBkp4IIQENVC5Fg2YHsvdXN1+WRIxQ8hzHbHSBiqZ2l68B/yg8sE1a4S7dqhkaAedunShWPH+Q==",
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz",
+      "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==",
+      "license": "MIT",
       "dependencies": {
-        "@intlify/message-compiler": "12.0.0-alpha.3",
-        "@intlify/shared": "12.0.0-alpha.3"
+        "@intlify/message-compiler": "9.14.5",
+        "@intlify/shared": "9.14.5"
       },
       "engines": {
         "node": ">= 16"
@@ -249,11 +252,12 @@
       }
     },
     "node_modules/@intlify/message-compiler": {
-      "version": "12.0.0-alpha.3",
-      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-12.0.0-alpha.3.tgz",
-      "integrity": "sha512-mDDTN3gfYOHhBnpnlby19UHyvMaOnzdlpsIrxUfs44R/vCATfn8pMOkE8PXD2t410xkocEj3FpDcC9XC/0v4Dg==",
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
+      "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==",
+      "license": "MIT",
       "dependencies": {
-        "@intlify/shared": "12.0.0-alpha.3",
+        "@intlify/shared": "9.14.5",
         "source-map-js": "^1.0.2"
       },
       "engines": {
@@ -264,9 +268,10 @@
       }
     },
     "node_modules/@intlify/shared": {
-      "version": "12.0.0-alpha.3",
-      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-12.0.0-alpha.3.tgz",
-      "integrity": "sha512-ryaNYBvxQjyJUmVuBBg+HHUsmGnfxcEUPR0NCeG4/K9N2qtyFE35C80S15IN6iYFE2MGWLN7HfOSyg0MXZIc9w==",
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz",
+      "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==",
+      "license": "MIT",
       "engines": {
         "node": ">= 16"
       },
@@ -274,22 +279,6 @@
         "url": "https://github.com/sponsors/kazupon"
       }
     },
-    "node_modules/@intlify/vue-i18n-core": {
-      "version": "12.0.0-alpha.3",
-      "resolved": "https://registry.npmmirror.com/@intlify/vue-i18n-core/-/vue-i18n-core-12.0.0-alpha.3.tgz",
-      "integrity": "sha512-YwAfTQILHN+VoK0P/Yv47GbKnEf1lhfbliyVyW3knAL1EmT8m0m3rwffXJnwyQhYw8Jpx85CpL49WkSgyi6d/g==",
-      "dependencies": {
-        "@intlify/core-base": "12.0.0-alpha.3",
-        "@intlify/shared": "12.0.0-alpha.3",
-        "@vue/devtools-api": "^6.5.0"
-      },
-      "engines": {
-        "node": ">= 16"
-      },
-      "peerDependencies": {
-        "vue": "^3.0.0"
-      }
-    },
     "node_modules/@jridgewell/gen-mapping": {
       "version": "0.3.13",
       "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -387,6 +376,316 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@parcel/watcher": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
+      "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "detect-libc": "^1.0.3",
+        "is-glob": "^4.0.3",
+        "micromatch": "^4.0.5",
+        "node-addon-api": "^7.0.0"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher-android-arm64": "2.5.1",
+        "@parcel/watcher-darwin-arm64": "2.5.1",
+        "@parcel/watcher-darwin-x64": "2.5.1",
+        "@parcel/watcher-freebsd-x64": "2.5.1",
+        "@parcel/watcher-linux-arm-glibc": "2.5.1",
+        "@parcel/watcher-linux-arm-musl": "2.5.1",
+        "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+        "@parcel/watcher-linux-arm64-musl": "2.5.1",
+        "@parcel/watcher-linux-x64-glibc": "2.5.1",
+        "@parcel/watcher-linux-x64-musl": "2.5.1",
+        "@parcel/watcher-win32-arm64": "2.5.1",
+        "@parcel/watcher-win32-ia32": "2.5.1",
+        "@parcel/watcher-win32-x64": "2.5.1"
+      }
+    },
+    "node_modules/@parcel/watcher-android-arm64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+      "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-arm64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+      "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-x64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+      "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-freebsd-x64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+      "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-glibc": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+      "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-musl": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+      "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-glibc": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+      "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-musl": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+      "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-glibc": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+      "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-musl": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+      "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-arm64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+      "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-ia32": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+      "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-x64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+      "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
     "node_modules/@polka/url": {
       "version": "1.0.0-next.29",
       "resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.29.tgz",
@@ -2847,6 +3146,20 @@
         "npm": "1.2.8000 || >= 1.4.16"
       }
     },
+    "node_modules/detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "optional": true,
+      "bin": {
+        "detect-libc": "bin/detect-libc.js"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
     "node_modules/detect-node": {
       "version": "2.1.0",
       "resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz",
@@ -4114,6 +4427,13 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/immutable": {
+      "version": "5.1.4",
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
+      "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/import-fresh": {
       "version": "3.3.1",
       "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -5205,6 +5525,14 @@
         "tslib": "^2.0.3"
       }
     },
+    "node_modules/node-addon-api": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+      "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/node-fetch": {
       "version": "2.7.0",
       "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -6746,6 +7074,136 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
       "dev": true
     },
+    "node_modules/sass": {
+      "version": "1.94.2",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz",
+      "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chokidar": "^4.0.0",
+        "immutable": "^5.0.2",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher": "^2.4.1"
+      }
+    },
+    "node_modules/sass-loader": {
+      "version": "10.5.2",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.5.2.tgz",
+      "integrity": "sha512-vMUoSNOUKJILHpcNCCyD23X34gve1TS7Rjd9uXHeKqhvBG39x6XbswFDtpbTElj6XdMFezoWhkh5vtKudf2cgQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "klona": "^2.0.4",
+        "loader-utils": "^2.0.0",
+        "neo-async": "^2.6.2",
+        "schema-utils": "^3.0.0",
+        "semver": "^7.3.2"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      },
+      "peerDependencies": {
+        "fibers": ">= 3.1.0",
+        "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
+        "sass": "^1.3.0",
+        "webpack": "^4.36.0 || ^5.0.0"
+      },
+      "peerDependenciesMeta": {
+        "fibers": {
+          "optional": true
+        },
+        "node-sass": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/sass-loader/node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/sass-loader/node_modules/loader-utils": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+      "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "big.js": "^5.2.2",
+        "emojis-list": "^3.0.0",
+        "json5": "^2.1.2"
+      },
+      "engines": {
+        "node": ">=8.9.0"
+      }
+    },
+    "node_modules/sass-loader/node_modules/semver": {
+      "version": "7.7.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+      "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/sass/node_modules/chokidar": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+      "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "readdirp": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 14.16.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/sass/node_modules/readdirp": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+      "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 14.18.0"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
     "node_modules/sax": {
       "version": "1.4.3",
       "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.3.tgz",
@@ -7793,13 +8251,13 @@
       "dev": true
     },
     "node_modules/vue-i18n": {
-      "version": "12.0.0-alpha.3",
-      "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-12.0.0-alpha.3.tgz",
-      "integrity": "sha512-+KQgD9LJoHfGCdJh3gaLdVS/Sps1n860+6wsjyeNLWJeEofjdVH7KPjz4rAeBlTAUaIDlIjHoXQY0Lk+8B6S9w==",
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz",
+      "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==",
+      "license": "MIT",
       "dependencies": {
-        "@intlify/core-base": "12.0.0-alpha.3",
-        "@intlify/shared": "12.0.0-alpha.3",
-        "@intlify/vue-i18n-core": "12.0.0-alpha.3",
+        "@intlify/core-base": "9.14.5",
+        "@intlify/shared": "9.14.5",
         "@vue/devtools-api": "^6.5.0"
       },
       "engines": {

+ 4 - 2
package.json

@@ -14,7 +14,7 @@
     "postcss-pxtorem": "^6.1.0",
     "vant": "^4.9.21",
     "vue": "^3.2.13",
-    "vue-i18n": "^12.0.0-alpha.3",
+    "vue-i18n": "^9.14.5",
     "vue-router": "^4.0.3",
     "vuex": "^4.0.0"
   },
@@ -31,7 +31,9 @@
     "babel-plugin-import": "^1.13.8",
     "less": "^4.0.0",
     "less-loader": "^8.0.0",
-    "postcss-pxtorem": "^6.1.0"
+    "postcss-pxtorem": "^6.1.0",
+    "sass": "^1.94.2",
+    "sass-loader": "^10.5.2"
   },
   "browserslist": [
     "> 1%",

+ 2 - 1
src/api/index.js

@@ -24,7 +24,8 @@ export function GetCandlestickChart(id) {
     method: 'get',
     params: {
         interval: id.period,
-        limit: 150
+        limit: 150,
+        end_time: id.to || undefined,
     }
   })
 }

+ 110 - 72
src/locales/index.js

@@ -1,105 +1,143 @@
 import { createI18n } from 'vue-i18n'
 import { Locale } from 'vant'
 
-// 1. 静态引入:确保路径正确 (根据你的截图 ./lang/ 是对的)
-import userZhCN from './lang/zh-CN.js'
-import vantZhCN from 'vant/es/locale/lang/zh-CN'
-
-// 2. 数据清洗:确保拿到的是纯净的对象,而不是 Module
-const userData = userZhCN.default || userZhCN
-const vantData = vantZhCN.default || vantZhCN
-
-// 3. 深度合并:将 Vant 和 User 数据合并
-// ⚠️ 注意顺序:userData 在后,确保用户自定义的优先级更高
-const finalZhCN = { ...vantData, ...userData }
-
-// 🔍 再次确认日志
-console.log('🚀 i18n Data Ready:', {
-  hasCommon: !!finalZhCN.common,
-  switchLang: finalZhCN.common?.switchLang
-})
+// --- 1. 静态引入默认语言 (兜底保障) ---
+// 使用相对路径,确保构建工具能正确解析
+import defaultZhCN from './lang/zh-CN.js'
+import defaultVantZhCN from 'vant/es/locale/lang/zh-CN'
+
+// --- 2. 工具函数:模块标准化 ---
+// 解决 import() 导入后可能是 Module 对象也可能是直接对象的问题
+const unwrap = (module) => (module && module.default ? module.default : module) || {}
+
+// --- 3. 初始数据准备 ---
+const userMessages = unwrap(defaultZhCN)
+const vantMessages = unwrap(defaultVantZhCN)
+
+// 初始加载时仅包含中文,其他语言按需加载
+const messages = {
+  'zh-CN': userMessages
+}
+
+// 记录已加载的语言,避免重复网络请求
+const loadedLanguages = ['zh-CN']
 
-const savedLang = localStorage.getItem('app-lang') || 'zh-CN'
+// --- 4. Vant 语言包映射表 (配置中心) ---
+// 将 Vant 的语言包路径映射到你的 app-lang 代码
+// 生产环境建议按需配置,不要全部引入
+const vantLocalesMap = {
+  'zh-CN': () => Promise.resolve({ default: defaultVantZhCN }), // 静态
+  'en-US': () => import('vant/es/locale/lang/en-US'),
+  'de-DE': () => import('vant/es/locale/lang/de-DE'),
+  'es-ES': () => import('vant/es/locale/lang/es-ES'),
+  'fr-FR': () => import('vant/es/locale/lang/fr-FR'),
+  'pt-PT': () => import('vant/es/locale/lang/pt-BR'), // Vant 可能只有 pt-BR
+  'id-ID': () => import('vant/es/locale/lang/id-ID'),
+  'ru-RU': () => import('vant/es/locale/lang/ru-RU'),
+}
 
-const i18n = createI18n({
-  legacy: false,
-  locale: savedLang,
-  fallbackLocale: 'zh-CN',
-  globalInjection: true,
-  // 4. 初始化消息:这里是关键,确保 zh-CN 永远有值
-  messages: {
-    'zh-CN': finalZhCN
+// --- 5. 初始化 i18n 实例 ---
+// 优先从 sessionStorage 读取,无则回退到 zh-CN
+const getInitialLang = () => {
+  const cached = sessionStorage.getItem('app-lang')
+  if (cached && cached !== 'null' && cached !== 'undefined') {
+    return cached
   }
+  return 'zh-CN'
+}
+
+const savedLang = getInitialLang()
+
+const i18n = createI18n({
+  legacy: false, // 组合式 API 模式
+  locale: savedLang, // 初始语言
+  fallbackLocale: 'zh-CN', // 翻译缺失时回退
+  globalInjection: true, // 全局注入 $t
+  messages // 初始消息池
 })
 
-// 记录已加载语言
-const loadedLanguages = ['zh-CN']
+// 初始应用 Vant 配置 (如果是中文直接应用,其他语言由 main.js 调用 loadLanguageAsync 触发)
+if (savedLang === 'zh-CN') {
+  Locale.use('zh-CN', vantMessages)
+}
 
-// 5. 设置 Vant 语言的辅助函数
-function setVantLanguage(lang, messages) {
-  if (lang === 'zh-CN') {
-    Locale.use('zh-CN', vantData)
-  } else {
-    Locale.use(lang, messages)
+// --- 6. 核心逻辑:设置语言环境 (副作用管理) ---
+function setI18nLanguage(lang, vantMsg = null) {
+  // 1. 切换 Vue I18n
+  i18n.global.locale.value = lang
+
+  // 2. 切换 HTML 属性 (SEO & 浏览器识别)
+  if (typeof document !== 'undefined') {
+    document.querySelector('html').setAttribute('lang', lang)
+  }
+
+  // 3. 持久化存储
+  sessionStorage.setItem('app-lang', lang)
+
+  // 4. 切换 Vant 组件语言
+  // 如果没传 vantMsg,尝试去内存找,或者回退到默认
+  if (vantMsg) {
+    Locale.use(lang, vantMsg)
+  } else if (lang === 'zh-CN') {
+    Locale.use('zh-CN', vantMessages)
   }
+
+  return lang
 }
 
+// --- 7. 导出:异步加载函数 ---
 export async function loadLanguageAsync(lang) {
-  // 空值检查
-  if (!lang || lang === 'undefined' || lang === 'null') {
+  // 参数校验
+  if (!lang || typeof lang !== 'string') {
+    // console.warn(`[i18n] Invalid lang: ${lang}, fallback to zh-CN`)
     return loadLanguageAsync('zh-CN')
   }
 
-  // A. 如果是中文:直接切过去,千万不要重新加载,防止覆盖
-  if (lang === 'zh-CN') {
-    i18n.global.locale.value = 'zh-CN'
-    setVantLanguage('zh-CN')
-    document.querySelector('html').setAttribute('lang', 'zh-CN')
-    localStorage.setItem('app-lang', 'zh-CN')
-    return 'zh-CN'
-  }
-
-  // B. 如果已加载:直接切
+  // 情况 A: 语言已加载 (包含静态的 zh-CN)
   if (loadedLanguages.includes(lang)) {
-    i18n.global.locale.value = lang
-    setVantLanguage(lang, i18n.global.getLocaleMessage(lang)) // 尝试从 i18n 实例获取 vant 数据有点难,这里简化处理
-    document.querySelector('html').setAttribute('lang', lang)
-    localStorage.setItem('app-lang', lang)
+    // 如果还没切换过去,执行切换
+    if (i18n.global.locale.value !== lang) {
+      // 尝试重新获取 Vant 语言配置 (通常不需要,因为 Vant 是全局单例,但为了保险)
+      return setI18nLanguage(lang)
+    }
     return lang
   }
 
-  // C. 动态加载新语言
+  // 情况 B: 新语言,需要动态加载
   try {
-    // 动态导入映射
-    // ⚠️ 确保这些文件在 src/locales/lang/ 目录下存在
-    const userImport = import(`./lang/${lang}.js`)
+    // 并行加载:用户业务翻译 + Vant 组件翻译
+    // 这里的 import 路径必须能被 Vite/Webpack 静态分析到
+    const userMsgPromise = import(`./lang/${lang}.js`)
 
-    // Vant 映射
-    let vantImportPromise = Promise.resolve({ default: {} })
-    if (lang === 'en-US') vantImportPromise = import('vant/es/locale/lang/en-US')
-    // ... 可以按需添加其他 Vant 语言 ...
+    // 查找 Vant 映射,如果没有则返回空对象防止报错
+    const vantMsgPromise = vantLocalesMap[lang]
+      ? vantLocalesMap[lang]()
+      : Promise.resolve({ default: {} })
 
-    const [userMod, vantMod] = await Promise.all([userImport, vantImportPromise])
+    const [userMsgModule, vantMsgModule] = await Promise.all([
+      userMsgPromise,
+      vantMsgPromise
+    ])
 
-    const userMsg = userMod.default || userMod
-    const vantMsg = vantMod.default || vantMod
+    const finalUserMsg = unwrap(userMsgModule)
+    const finalVantMsg = unwrap(vantMsgModule)
 
-    const finalMsg = { ...vantMsg, ...userMsg }
+    // 将用户翻译注入 i18n
+    i18n.global.setLocaleMessage(lang, finalUserMsg)
 
-    i18n.global.setLocaleMessage(lang, finalMsg)
+    // 标记已加载
     loadedLanguages.push(lang)
 
-    // 切换
-    i18n.global.locale.value = lang
-    setVantLanguage(lang, vantMsg)
-    document.querySelector('html').setAttribute('lang', lang)
-    localStorage.setItem('app-lang', lang)
+    // 应用切换
+    return setI18nLanguage(lang, finalVantMsg)
 
-    return lang
   } catch (e) {
-    console.error(`❌ Load lang ${lang} failed:`, e)
-    // 失败回退到中文
-    return loadLanguageAsync('zh-CN')
+    // console.error(`[i18n] Failed to load language: ${lang}`, e)
+    // 加载失败时,如果目标不是中文,则回退到中文
+    if (lang !== 'zh-CN') {
+      return loadLanguageAsync('zh-CN')
+    }
+    throw e // 如果中文也挂了,抛出异常让 main.js 处理
   }
 }
 

+ 2 - 3
src/locales/lang/en-US.js

@@ -1,7 +1,5 @@
 export default {
   common: {
-      // switchLang: '*** ENGLISH ***',
-    // confirm: '*** SUBMIT ***',
     switchLang: 'Switch Language',
     confirm: 'Submit',
     cancel: 'Cancel',
@@ -29,4 +27,5 @@ export default {
     pleaseSelect: 'Select',
     pleaseInput: 'Enter'
   }
-}
+}
+

+ 38 - 26
src/main.js

@@ -2,12 +2,11 @@ import { createApp } from "vue";
 import App from "./App.vue";
 import 'amfe-flexible';
 import router from "./router";
-// import store from "./store";
 import 'vant/lib/index.css';
 import "./assets/less/index.less";
 import "./assets/h5-reset.css";
 
-// 引入 i18n
+// 引入 i18n(确保 locales/index.js 导出默认 i18n 实例和 loadLanguageAsync)
 import i18n, { loadLanguageAsync } from './locales/index.js';
 
 import {
@@ -16,27 +15,40 @@ import {
 
 import api from "./utils/api.js";
 
-const savedLang = localStorage.getItem('app-lang') || 'zh-CN';
-
-// 🚀 启动逻辑
-loadLanguageAsync(savedLang)
-  .catch(err => {
-    // 即使加载语言失败,也要强行启动 App,避免白屏
-    console.error('Failed to load language, falling back to rendering...', err);
-  })
-  .then(() => {
-    const app = createApp(App);
-
-    // Vant 组件注册
-    app.use(Button).use(Popup).use(Form).use(Field).use(NavBar).use(Picker).use(Icon).use(Toast);
-
-    // 核心插件
-    app.use(i18n);   // ✅ 此时 i18n 内部应该已经有数据了
-    app.use(router);
-    // app.use(store);
-
-    app.config.globalProperties.$api = api;
-
-    app.mount("#app");
-    console.log('🚀 App Mounted Successfully');
-  });
+// ✅ 改为 sessionStorage
+const savedLang = sessionStorage.getItem('app-lang') || 'zh-CN';
+// console.log('🚀 [main.js] 启动语言:', savedLang);
+
+async function bootstrap() {
+  try {
+    // 等待语言包加载并注入 i18n(很关键)
+    await loadLanguageAsync(savedLang);
+    // console.log('🚀 [main.js] 语言加载完成:', savedLang);
+  } catch (err) {
+    // console.error('❌ [main.js] 语言加载失败,回退到 zh-CN', err);
+    // 尝试回退到默认中文(如果 loadLanguageAsync 能接受直接传 'zh-CN')
+    // try { await loadLanguageAsync('zh-CN'); } catch (e) { console.error('❌ 回退中文也失败', e); }
+  }
+
+  console.log('🚀 [main.js] 语言准备就绪,挂载 App...');
+  const app = createApp(App);
+
+  // 先挂载 i18n,确保组件内 t() 能拿到消息
+  if (i18n) {
+    app.use(i18n);
+  } else {
+    // console.error('❌ [main.js] i18n 实例丢失!');
+  }
+
+  // 注册 Vant 组件
+  app.use(Button).use(Popup).use(Form).use(Field).use(NavBar).use(Picker).use(Icon).use(Toast);
+
+  // 再挂载路由和其它
+  app.use(router);
+  app.config.globalProperties.$api = api;
+
+  app.mount("#app");
+  console.log('✅ [main.js] App 挂载成功');
+}
+
+bootstrap();

+ 1 - 1
src/utils/request.js

@@ -67,7 +67,7 @@ service.interceptors.response.use(
     console.log('网络报错' + error) // for debug
     let message = error.message
     
-    if (message == 'Network Error') {
+    if (message === 'Network Error') {
       message = '后端接口连接异常'
     } else if (message.includes('timeout')) {
       message = '系统接口请求超时'

Різницю між файлами не показано, бо вона завелика
+ 7 - 1
src/views/bitcoin/TradeFutures.vue


Різницю між файлами не показано, бо вона завелика
+ 14 - 0
src/views/bitcoin/lever/CryptocurrencyTrading.vue


+ 1 - 1
src/views/bitcoin/lever/TradeSeconds.vue

@@ -65,7 +65,7 @@
       <!-- 引入封装好的组件,只需传数据和高度 -->
       <KlineChart
         :data="kLineData"
-        height="100%"
+        height="70vh"
       />
     </div>
 

+ 66 - 42
src/views/bitcoin/lever/components/KLineChart.vue

@@ -8,6 +8,8 @@
 import { onMounted, onBeforeUnmount, ref, watch, nextTick, toRaw, defineExpose } from 'vue'
 import * as klinecharts from 'klinecharts'
 
+const emit = defineEmits(['load-more'])
+
 const props = defineProps({
   data: { type: Array, default: () => [] },
   height: { type: String, default: '100%' },
@@ -17,7 +19,7 @@ const props = defineProps({
     default: () => ({
       up: '#2EBD85',
       down: '#F6465D',
-      grid: '#F2F4F6', // 浅色网格
+      grid: '#F2F4F6',
       text: '#929AA5',
       targetLine: '#4A6EF5'
     })
@@ -33,7 +35,6 @@ const initChart = () => {
 
   chartInstance = klinecharts.init(chartContainer.value)
 
-  // 1. 设置精度
   const { price, volume } = props.precision
   if (chartInstance.setPriceVolumePrecision) {
     chartInstance.setPriceVolumePrecision(price, volume)
@@ -41,7 +42,6 @@ const initChart = () => {
     chartInstance.setPrecision(price, volume)
   }
 
-  // 2. 样式配置
   const { up, down, grid, text } = props.colors
   chartInstance.setStyleOptions({
     grid: { show: true, horizontal: { show: true, size: 1, color: grid, style: 'dash', dashValue: [5, 5] }, vertical: { show: false } },
@@ -54,41 +54,36 @@ const initChart = () => {
     yAxis: { inside: true, axisLine: { show: false }, tickLine: { show: false }, tickText: { color: text, size: 10, paddingLeft: 4 } },
   })
 
-  // 3. 默认指标: 主图 MA
-  chartInstance.createTechnicalIndicator('MA', false, { id: 'candle_pane' })
+  // 监听加载更多
+  chartInstance.loadMore(timestamp => {
+    emit('load-more', timestamp)
+  })
 
-  // 4. 默认两个副图 (初始就应用权重逻辑)
+  // 初始化指标
+  chartInstance.createTechnicalIndicator('MA', false, { id: 'candle_pane' })
   chartInstance.createTechnicalIndicator('VOL', false, { id: 'pane_1', dragEnabled: false })
   chartInstance.createTechnicalIndicator('MACD', false, { id: 'pane_2', dragEnabled: false })
 
-  // 🔥 关键初始化:强制分配权重
-  // 主图权重 8,副图权重 1
   chartInstance.setPaneOptions({ id: 'candle_pane', heightRatio: 8 })
   chartInstance.setPaneOptions({ id: 'pane_1', heightRatio: 1 })
   chartInstance.setPaneOptions({ id: 'pane_2', heightRatio: 1 })
 
-  // 5. 加载数据
   if (props.data && props.data.length > 0) {
     chartInstance.applyNewData(toRaw(props.data))
   }
 }
 
-// --- 设置主图指标 ---
 const setMainIndicator = (name) => {
   if (!chartInstance) return
   const mains = ['MA', 'EMA', 'BOLL', 'SAR', 'AVL', 'TRIX']
   mains.forEach(m => chartInstance.removeTechnicalIndicator('candle_pane', m))
-
   if (name && name !== 'Hide') {
     chartInstance.createTechnicalIndicator(name, false, { id: 'candle_pane' })
   }
 }
 
-// --- 🟢 核心修复:基于权重的动态高度控制 ---
 const setSubIndicators = (names) => {
   if (!chartInstance) return
-
-  // 1. 彻底清除旧指标
   const paneIds = ['pane_1', 'pane_2', 'pane_3', 'pane_4', 'pane_5']
   const allSubs = ['VOL', 'MACD', 'KDJ', 'RSI', 'WR', 'CCI', 'OBV', 'AO', 'DMA', 'TRIX', 'BRAR', 'VR']
 
@@ -97,54 +92,84 @@ const setSubIndicators = (names) => {
   })
 
   const validNames = names.filter(n => n && n !== 'Hide')
-
-  // 2. 重新创建并分配权重
-  // 核心思路:主图(candle_pane)永远设为 8,每个副图设为 1
-  // 这样无论加多少个副图,主图都会占据绝大部分空间
-
-  // 步骤A: 必须先显式重置主图权重,确保它是“老大”
-  chartInstance.setPaneOptions({
-    id: 'candle_pane',
-    heightRatio: 8
-  })
+  chartInstance.setPaneOptions({ id: 'candle_pane', heightRatio: 8 })
 
   validNames.forEach((name, index) => {
     const paneId = `pane_${index + 1}`
+    chartInstance.createTechnicalIndicator(name, false, { id: paneId, dragEnabled: false })
+    chartInstance.setPaneOptions({ id: paneId, heightRatio: 1 })
+  })
+}
 
-    // 步骤B: 创建指标
-    chartInstance.createTechnicalIndicator(name, false, {
-      id: paneId,
-      dragEnabled: false
-    })
+// ---------------------------------------------------------
+// 核心修复区域:applyHistoryData
+// ---------------------------------------------------------
+const applyHistoryData = (dataList, hasMore) => {
+  if (!chartInstance) return
 
-    // 步骤C: 强制给副图分配“小弟”权重 (1)
-    chartInstance.setPaneOptions({
-      id: paneId,
-      heightRatio: 1
-    })
-  })
+  // 1. 获取当前图表里【最左侧/最早】的一条数据
+  const currentDataList = chartInstance.getDataList()
+  const firstCurrentParams = currentDataList[0]
+
+  // 2. 只有当图表已有数据,且新加载的数据不为空时,才进行去重检测
+  if (firstCurrentParams && dataList.length > 0) {
+    // 获取新历史数据的【最后一条】(因为传入的 dataList 是按时间升序排列的)
+    // 这条数据是时间最晚的,最容易和 currentDataList[0] 重叠
+    const lastHistoryParams = dataList[dataList.length - 1]
+
+    // 3. 如果时间戳完全相等,说明重叠了
+    if (lastHistoryParams.timestamp === firstCurrentParams.timestamp) {
+      // 移除新加载数据中的最后一条 (保留图表中已有的那条)
+      // 原因:通常图表里的数据可能已经被 WS 更新过,或者为了避免 UI 闪烁,不替换边界点
+      dataList.pop()
+    }
+  }
+
+  // 4. 将处理后的数据拼接到左侧
+  chartInstance.applyMoreData(dataList, hasMore)
 }
 
 defineExpose({
   setMainIndicator,
-  setSubIndicators
+  setSubIndicators,
+  applyHistoryData
 })
 
-// --- 数据监听 ---
 watch(() => props.data, (newData) => {
   if (!chartInstance) return
   const rawData = toRaw(newData)
   const currentList = chartInstance.getDataList()
 
-  if (rawData.length === 0) { chartInstance.clearData(); return }
-  if (currentList.length === 0) { chartInstance.applyNewData(rawData); return }
+  if (rawData.length === 0) {
+    chartInstance.clearData();
+    return
+  }
+
+  if (currentList.length === 0) {
+    chartInstance.applyNewData(rawData);
+    return
+  }
 
   const firstOld = currentList[0]
   const firstNew = rawData[0]
-  if (firstOld.timestamp !== firstNew.timestamp) {
-    chartInstance.applyNewData(rawData)
+  const lastOld = currentList[currentList.length - 1]
+  const lastNew = rawData[rawData.length - 1]
+
+  // 防止历史数据回弹 (Safety check)
+  if (firstNew.timestamp < firstOld.timestamp) {
     return
   }
+
+  // 切换 Symbol 或周期时的全量更新检测
+  if (lastNew.timestamp !== lastOld.timestamp && rawData.length > 1) {
+     const isUpdate = lastNew.timestamp > lastOld.timestamp && rawData.length === currentList.length + 1;
+     if (!isUpdate) {
+        chartInstance.applyNewData(rawData)
+        return
+     }
+  }
+
+  // WS 实时更新
   if (rawData.length > 0) {
     const lastData = rawData[rawData.length - 1]
     chartInstance.updateData(lastData)
@@ -177,7 +202,6 @@ const handleResize = () => {
 </script>
 
 <style scoped>
-/* 亮色背景配置 */
 .kline-wrapper { width: 100%; position: relative; background: #ffffff; }
 .kline-chart { width: 100%; height: 100%; }
 </style>

+ 452 - 0
src/views/bitcoin/lever/components/UserAssetsPanel.vue

@@ -0,0 +1,452 @@
+<template>
+  <div class="panel-container">
+    <div class="tabs-header">
+      <div class="tabs-left">
+        <div
+          class="tab-item"
+          :class="{ active: activeTab === 'assets' }"
+          @click="activeTab = 'assets'"
+        >
+          币种资产
+        </div>
+        <div
+          class="tab-item"
+          :class="{ active: activeTab === 'positions' }"
+          @click="activeTab = 'positions'"
+        >
+          持有仓位({{ positionsList.length }})
+        </div>
+        <div
+          class="tab-item"
+          :class="{ active: activeTab === 'orders' }"
+          @click="activeTab = 'orders'"
+        >
+          当前委托({{ ordersList.length }})
+        </div>
+      </div>
+      <div class="tabs-right" @click="router.push('/historyIndex')">
+        <span class="history-icon">
+          <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <path d="M17.025 10.125L15.5254 8.625L14.025 10.125M15.75 9C15.75 12.7279 12.7279 15.75 9 15.75C5.27208 15.75 2.25 12.7279 2.25 9C2.25 5.27208 5.27208 2.25 9 2.25C11.4764 2.25 13.6414 3.5836 14.8159 5.57182M9 5.25V9L11.25 10.5" stroke="#A8A8A8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+          </svg>
+        </span> 全部
+      </div>
+    </div>
+
+    <div class="panel-content">
+
+      <div v-if="activeTab === 'assets'" class="list-container">
+        <div v-for="(item, index) in assetsList" :key="index" class="card asset-card">
+          <div class="card-header">
+            <div class="coin-info">
+              <div class="coin-icon icon-btc">₿</div>
+              <div class="coin-name">
+                <span class="symbol">{{ item.symbol }}</span>
+                <span class="name-cn">{{ item.nameCn }}</span>
+              </div>
+            </div>
+            <div class="asset-total">
+              <div class="main-val">{{ item.totalValue }}</div>
+              <div class="sub-val">≈ ¥ {{ item.cnyValue }}</div>
+            </div>
+          </div>
+          <div class="divider"></div>
+          <div class="card-row">
+            <span class="label">可用</span>
+            <span class="value">{{ item.available }}</span>
+          </div>
+          <div class="card-row">
+            <span class="label">冻结</span>
+            <span class="value red-text">{{ item.frozen }}</span>
+          </div>
+        </div>
+      </div>
+
+      <div v-else-if="activeTab === 'positions'" class="list-container">
+        <div v-for="(item, index) in positionsList" :key="index" class="card position-card">
+          <div class="card-header">
+            <div class="coin-info">
+              <div class="coin-icon icon-btc">₿</div>
+              <div class="coin-name-block">
+                <span class="symbol">{{ item.pair }}</span>
+                <div class="tags-row">
+                  <span class="tag" :class="item.type === 'buy' ? 'tag-green' : 'tag-red'">
+                    {{ item.type === 'buy' ? '买入' : '卖出' }}
+                  </span>
+                  <span class="tag tag-grey">{{ item.leverage }}X</span>
+                  <span class="tag tag-grey">市价</span>
+                </div>
+              </div>
+            </div>
+            <div class="header-right-time">
+              {{ item.time }} <br> 下单时间
+            </div>
+          </div>
+          <div class="divider"></div>
+          <div class="data-grid">
+            <div class="data-col">
+              <div class="label">倍数</div>
+              <div class="val">{{ item.leverage }}X</div>
+            </div>
+            <div class="data-col center">
+              <div class="label">数量</div>
+              <div class="val">{{ item.amount }} {{ item.unit }}</div>
+            </div>
+            <div class="data-col right">
+              <div class="label">价格</div>
+              <div class="val">{{ item.price }} {{ item.unit }}</div>
+            </div>
+          </div>
+          <button class="btn-block-red">平仓</button>
+        </div>
+      </div>
+
+      <div v-else-if="activeTab === 'orders'" class="list-container">
+        <div v-for="(item, index) in ordersList" :key="index" class="card order-card">
+          <div class="card-header">
+            <div class="coin-info">
+              <div class="coin-icon icon-btc">₿</div>
+              <div class="coin-name-block">
+                <span class="symbol">{{ item.pair }}</span>
+                <div class="tags-row">
+                  <span class="tag" :class="item.type === 'buy' ? 'tag-green' : 'tag-red'">
+                    {{ item.type === 'buy' ? '买入' : '卖出' }}
+                  </span>
+                  <span class="tag tag-grey">{{ item.leverage }}X</span>
+                  <span class="tag tag-grey">市价</span>
+                </div>
+              </div>
+            </div>
+            <button class="btn-small-red">撤单</button>
+          </div>
+          <div class="divider"></div>
+          <div class="data-grid">
+            <div class="data-col">
+              <div class="label">数量</div>
+              <div class="val">{{ item.amount }} {{ item.unit }}</div>
+            </div>
+            <div class="data-col center">
+              <div class="label">下单时间</div>
+              <div class="val">{{ item.time }}</div>
+            </div>
+            <div class="data-col right">
+              <div class="label">价格</div>
+              <div class="val">{{ item.price }} {{ item.unit }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useRouter } from 'vue-router'; // 【新增】 引入路由
+
+const router = useRouter(); // 【新增】 实例化路由
+
+// 当前选中的 Tab:'assets', 'positions', 'orders'
+const activeTab = ref('orders');
+
+// 模拟数据 - 委托
+const ordersList = ref([
+  {
+    pair: 'BTC/USDT',
+    type: 'buy',
+    leverage: 20,
+    amount: '0.215',
+    unit: 'USDT',
+    price: '0.215',
+    time: '2025-11-04, 16:30'
+  },
+  {
+    pair: 'BTC/USDT',
+    type: 'sell',
+    leverage: 20,
+    amount: '0.215',
+    unit: 'USDT',
+    price: '0.215',
+    time: '2025-11-04, 16:30'
+  }
+]);
+
+// 模拟数据 - 仓位
+const positionsList = ref([
+  {
+    pair: 'BTC/USDT',
+    type: 'buy',
+    leverage: 200,
+    amount: '0.215',
+    unit: 'USDT',
+    price: '0.215',
+    time: '2025-11-04, 16:30'
+  },
+  {
+    pair: 'ETH/USDT',
+    type: 'sell',
+    leverage: 50,
+    amount: '1.5',
+    unit: 'USDT',
+    price: '1800.00',
+    time: '2025-11-04, 14:20'
+  }
+]);
+
+// 模拟数据 - 资产
+const assetsList = ref([
+  {
+    symbol: 'BTC',
+    nameCn: '比特币',
+    totalValue: '1,125,158.00',
+    cnyValue: '2150508',
+    available: '18802.0850',
+    frozen: '151.2050'
+  },
+  {
+    symbol: 'BTC', // 重复模拟图3效果
+    nameCn: '比特币',
+    totalValue: '1,125,158.00',
+    cnyValue: '2150508',
+    available: '18802.0850',
+    frozen: '151.2050'
+  }
+]);
+</script>
+
+<style scoped lang="scss">
+/* 基础变量 */
+$c-bg: #fff;
+$c-white: #ffffff;
+$c-text-main: #333333;
+$c-text-sub: #969799;
+$c-green: #00b86b; /* 买入绿 */
+$c-red: #dc3545;   /* 卖出/撤单红 */
+$c-line: #f5f5f5;
+$c-orange: #f7931a; /* BTC颜色 */
+
+.panel-container {
+  width: 100%;
+  background-color: $c-bg;
+  font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Segoe UI, Arial, Roboto, "PingFang SC", "miui", "Hiragino Sans GB", "Microsoft Yahei", sans-serif;
+  padding-bottom: 20px;
+}
+
+/* Tab 头部样式 */
+.tabs-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background: $c-white;
+  padding: 12px 16px;
+  position: sticky;
+  top: 0;
+  z-index: 10;
+}
+
+.tabs-left {
+  display: flex;
+  gap: 20px;
+}
+
+.tab-item {
+  font-size: 15px;
+  color: $c-text-sub;
+  font-weight: 400;
+  position: relative;
+  cursor: pointer;
+  padding-bottom: 4px;
+  transition: all 0.2s;
+
+  &.active {
+    color: $c-text-main;
+    font-weight: 600;
+    font-size: 16px;
+
+    &::after {
+      content: '';
+      position: absolute;
+      bottom: -2px;
+      left: 50%;
+      transform: translateX(-50%);
+      width: 20px;
+      height: 3px;
+      background-color: #000;
+      border-radius: 2px;
+    }
+  }
+}
+
+.tabs-right {
+  font-size: 13px;
+  color: $c-text-sub;
+  display: flex;
+  align-items: flex-start;
+  gap: 4px;
+  margin-top: 2px;
+}
+
+/* 列表容器 */
+.list-container {
+  padding: 10px 16px;
+}
+
+/* 卡片通用样式 */
+.card {
+  background: $c-white;
+  border-radius: 8px;
+  padding: 12px 12px;
+  margin-bottom: 12px;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.02);
+  border: 1px solid #ebedf0;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 12px;
+}
+
+.coin-info {
+  display: flex;
+  align-items: flex-start;
+}
+
+.coin-icon {
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  background: $c-orange;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 20px;
+  font-weight: bold;
+  margin-right: 10px;
+}
+
+.coin-name-block {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.symbol {
+  font-size: 16px;
+  font-weight: 600;
+  color: $c-text-main;
+}
+
+/* 标签样式 */
+.tags-row {
+  display: flex;
+  gap: 4px;
+}
+
+.tag {
+  font-size: 10px;
+  padding: 1px 4px;
+  border-radius: 2px;
+  color: #fff;
+  display: inline-block;
+  line-height: 1.4;
+}
+
+.tag-green { background: $c-green; }
+.tag-red { background: $c-red; }
+.tag-grey { background: #c8c9cc; color: #fff; }
+
+/* 分割线 */
+.divider {
+  height: 1px;
+  background: $c-line;
+  margin: 0 0 12px 0;
+}
+
+/* 数据网格 (三列) */
+.data-grid {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+
+.data-col {
+  text-align: left;
+
+
+  &.center { text-align: center; width: 50%; }
+  &.right { text-align: right; }
+
+  .label {
+    font-size: 12px;
+    color: $c-text-sub;
+    margin-bottom: 4px;
+  }
+  .val {
+    font-size: 14px;
+    color: $c-text-main;
+    font-weight: 500;
+  }
+}
+
+/* 按钮样式 */
+.btn-small-red {
+  background: $c-red;
+  color: #fff;
+  border: none;
+  padding: 6px 16px;
+  border-radius: 4px;
+  font-size: 13px;
+}
+
+.btn-block-red {
+  width: 100%;
+  background: $c-red;
+  color: #fff;
+  border: none;
+  padding: 10px 0;
+  border-radius: 20px; /* 圆角按钮 */
+  font-size: 15px;
+  margin-top: 8px;
+  font-weight: 500;
+}
+
+/* 特殊样式:Position 页面的时间 */
+.header-right-time {
+  text-align: right;
+  font-size: 12px;
+  color: $c-text-sub;
+  line-height: 1.4;
+}
+
+/* 特殊样式:Asset 页面 */
+.asset-card {
+  .card-header {
+    align-items: center;
+  }
+  .coin-name {
+    display: flex;
+    flex-direction: column;
+    .name-cn { font-size: 12px; color: $c-text-sub; }
+  }
+  .asset-total {
+    text-align: right;
+    .main-val { font-size: 18px; font-weight: bold; color: $c-text-main; }
+    .sub-val { font-size: 12px; color: $c-text-sub; margin-top: 2px; }
+  }
+  .card-row {
+    display: flex;
+    justify-content: space-between;
+    font-size: 13px;
+    margin-bottom: 6px;
+    &:last-child { margin-bottom: 0; }
+
+    .label { color: $c-text-sub; }
+    .value { color: $c-text-sub; font-family: sans-serif; }
+    .red-text { color: $c-red; font-weight: 500; }
+  }
+}
+</style>

+ 1 - 1
src/views/market/Index.vue

@@ -486,7 +486,7 @@ const formatCNY = (val) => {
 }
 
 .col { display: flex; }
-.col-left { width: 38%; justify-content: flex-start; align-items: center; }
+.col-left { width: 52%; justify-content: flex-start; align-items: center; }
 .col-center { width: 25%; justify-content: center; align-items: center; margin-left: 10px; }
 /* 修正右侧列样式,支持排序图标 */
 .col-right { width: 37%; justify-content: flex-end; align-items: center; }

+ 4 - 4
src/views/market/details/EntrustingOrder.vue

@@ -26,10 +26,10 @@
         <div class="item-sell fs400 fs12 fcA8A8A8">{{ index + 1 }}</div>
       </div>
     </div>
-    <div class="buy-sell pf500 fs16 fcFFFFFF">
-      <div class="buy-btn">买入(做多)</div>
-      <div class="sell-btn">卖出(做空)</div>
-    </div>
+<!--    <div class="buy-sell pf500 fs16 fcFFFFFF">-->
+<!--      <div class="buy-btn">买入(做多)</div>-->
+<!--      <div class="sell-btn">卖出(做空)</div>-->
+<!--    </div>-->
   </div>
 </template>
 <script setup>

+ 4 - 4
src/views/market/details/LatestTransactions.vue

@@ -20,10 +20,10 @@
         <div class="item-number2 fs400 fs12 fc444444">{{ item.q }}</div>
       </div>
     </div>
-    <div class="buy-sell pf500 fs16 fcFFFFFF">
-      <div class="buy-btn">买入(做多)</div>
-      <div class="sell-btn">卖出(做空)</div>
-    </div>
+<!--    <div class="buy-sell pf500 fs16 fcFFFFFF">-->
+<!--      <div class="buy-btn">买入(做多)</div>-->
+<!--      <div class="sell-btn">卖出(做空)</div>-->
+<!--    </div>-->
   </div>
 </template>
 <script setup>

+ 123 - 96
src/views/market/details/MarketConditions.vue

@@ -62,37 +62,28 @@
         </div>
       </div>
 
-      <!-- 指标按钮 -->
       <div class="tab-item icon-btn" @click.stop="toggleIndicators">
-        <!-- 保持你的图标路径 -->
         <img src="@/assets/icon/bitcoin/lishidingdan.svg" alt="" />
       </div>
     </nav>
 
     <!-- 3. 指标设置面板 -->
     <div class="indicator-panel" v-show="showIndicatorMenu" @click.stop>
-      <!-- 主图设置 (单选) -->
       <div class="panel-section">
         <div class="section-title">主图指标</div>
         <div class="btn-group">
           <div v-for="m in ['MA', 'EMA', 'BOLL', 'SAR']" :key="m"
                class="idx-btn" :class="{ active: mainIdx === m }"
-               @click="changeMain(m)">
-            {{ m }}
-          </div>
+               @click="changeMain(m)">{{ m }}</div>
           <div class="idx-btn" :class="{ active: mainIdx === 'Hide' }" @click="changeMain('Hide')">隐藏</div>
         </div>
       </div>
-
-      <!-- 副图设置 (多选) -->
       <div class="panel-section">
         <div class="section-title">副图指标 (可多选)</div>
         <div class="btn-group">
           <div v-for="s in ['VOL', 'MACD', 'KDJ', 'RSI', 'WR', 'CCI', 'OBV']" :key="s"
                class="idx-btn" :class="{ active: subIdx.includes(s) }"
-               @click="toggleSub(s)">
-            {{ s }}
-          </div>
+               @click="toggleSub(s)">{{ s }}</div>
           <div class="idx-btn" :class="{ active: subIdx.length === 0 }" @click="clearSub">隐藏</div>
         </div>
       </div>
@@ -100,82 +91,74 @@
 
     <!-- 4. K线图组件 -->
     <div class="k-line-main">
-      <KlineChart
+      <KLineChart
         ref="klineRef"
         :data="kLineData"
         height="70vh"
-        :precision="{ price: getPricePrecision(marketInfo.price), volume: 2 }" />
+        :precision="{ price: getPricePrecision(marketInfo.price), volume: 2 }"
+        @load-more="handleLoadMore" />
     </div>
 
-    <!-- 5. 底部 -->
+    <!-- 5. 底部原有的 委托挂单/最新成交 -->
     <div class="notifi-classifi">
       <div class="pf600 fs14" :class="current === 'entrustingOrder' ? 'fc121212' : 'fcA8A8A8'" @click="messageChange('entrustingOrder')">委托挂单</div>
       <div class="sys-notifi pf600 fs14" :class="current === 'latestTransactions' ? 'fc121212' : 'fcA8A8A8'" @click="messageChange('latestTransactions')">最新成交</div>
     </div>
 
     <component :is="currentComponent" :symbol-id="symbolId" :latestTransactionData="latestTransactionData" :orderPlacement="orderPlacement" />
+
+    <div
+        style="
+    box-shadow: 0 -0.05333rem 0.26667rem rgba(109 109 109 / 5%);
+    box-sizing: border-box;margin-top: 20px;
+    width:100%;z-index: 99;position: sticky; bottom: 0;background-color: #FFFFFF;padding: 20px;">
+      <div @click="router.push('/bitcoin/CryptocurrencyTrading')"
+          style="font-size: 17px; margin:0 auto;border-radius:19px;color:#FFFFFF; text-align:center; width:80%; line-height: 38px; font-weight: 500; background-color: #F6465D; ">交易</div>
+    </div>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from "vue";
-import { useRoute } from "vue-router";
+import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
+import { useRoute,useRouter } from "vue-router";
 import { GetCandlestickChart } from "@/api/index.js";
+
+const router = useRouter(); // 【新增】 实例化路由
+
+// 引入组件
+import KLineChart from "@/views/bitcoin/lever/components/KLineChart.vue";
 import EntrustingOrder from "./EntrustingOrder.vue";
 import LatestTransactions from "./LatestTransactions.vue";
-import KlineChart from "@/views/bitcoin/lever/components/KLineChart.vue";
 
 const route = useRoute();
 const symbolId = computed(() => route.query.id || "6");
 
-// 周期
+// --- 周期 ---
 const currentTab = ref("1d");
 const visibleTabs = ["15m", "1h", "4h", "1d"];
 const moreTabs = ["1m", "5m", "30m", "1w", "1M"];
 const isMoreActive = computed(() => moreTabs.includes(currentTab.value));
 const getTabLabel = (t) => ({ '1m':'1分', '5m':'5分', '15m':'15分', '30m':'30分', '1h':'1小时', '4h':'4小时', '1d':'日线', '1w':'周线', '1M':'月线' }[t] || t);
 
-// --- 指标管理 (升级版) ---
+// --- 指标管理 ---
 const showMoreMenu = ref(false);
 const showIndicatorMenu = ref(false);
 const klineRef = ref(null);
-
-// 主图状态 (单选)
 const mainIdx = ref('MA');
-
-// 副图状态 (多选,默认选两个)
 const subIdx = ref(['VOL', 'MACD']);
 
-// 切换主图
-const changeMain = (name) => {
-  mainIdx.value = name;
-  if (klineRef.value) klineRef.value.setMainIndicator(name);
-};
-
-// 切换副图 (多选逻辑)
+const changeMain = (name) => { mainIdx.value = name; if (klineRef.value) klineRef.value.setMainIndicator(name); };
 const toggleSub = (name) => {
   const index = subIdx.value.indexOf(name);
-  if (index > -1) {
-    // 已经选中 -> 取消选中
-    subIdx.value.splice(index, 1);
-  } else {
-    // 未选中 -> 添加选中 (限制最多3个防止太挤,或者不做限制)
-    if (subIdx.value.length >= 3) {
-        subIdx.value.shift(); // 移除最早选的一个,保持最多3个
-    }
+  if (index > -1) subIdx.value.splice(index, 1);
+  else {
+    if (subIdx.value.length >= 3) subIdx.value.shift();
     subIdx.value.push(name);
   }
-  // 更新图表
   if (klineRef.value) klineRef.value.setSubIndicators(subIdx.value);
 };
+const clearSub = () => { subIdx.value = []; if (klineRef.value) klineRef.value.setSubIndicators([]); };
 
-// 隐藏所有副图
-const clearSub = () => {
-  subIdx.value = [];
-  if (klineRef.value) klineRef.value.setSubIndicators([]);
-};
-
-// --- 交互 ---
 const toggleMore = () => { showIndicatorMenu.value = false; showMoreMenu.value = !showMoreMenu.value; };
 const toggleIndicators = () => { showMoreMenu.value = false; showIndicatorMenu.value = !showIndicatorMenu.value; };
 const closePopups = () => { showMoreMenu.value = false; showIndicatorMenu.value = false; };
@@ -188,44 +171,92 @@ const switchPeriod = (period) => {
   getKlineData();
 };
 
-// --- 数据/WS (保持不变) ---
+// --- 数据/WS ---
 const kLineData = ref([]);
 const socket = ref(null);
 const WS_BASE_URL = "ws://backend.66linknow.com/ws/kline/";
 const HEARTBEAT_INTERVAL = 15000;
 const RECONNECT_DELAY = 3000;
 let heartbeatTimer = null, reconnectTimer = null, isUnmounted = false;
+const isLoadingMore = ref(false);
 
 const marketInfo = ref({ price: "0.00", fiatPrice: "0.00", change: 0.0, high: "0.00", low: "0.00", vol: "0", amount: "0" });
+
 const orderPlacement = ref();
 const latestTransactionData = ref();
 const current = ref("entrustingOrder");
 const componentsMap = { entrustingOrder: EntrustingOrder, latestTransactions: LatestTransactions };
 const currentComponent = computed(() => componentsMap[current.value]);
+const messageChange = (key) => current.value = key;
 
-const getKlineData = async () => {
+// --- 获取 K线数据 (核心) ---
+const getKlineData = async (endTime = null) => {
   if (typeof GetCandlestickChart !== "function") return;
+
   try {
-    const res = await GetCandlestickChart({ symbol: symbolId.value, period: currentTab.value });
+    const params = {
+      symbol: symbolId.value,
+      period: currentTab.value,
+      limit: 150,
+      to: endTime || undefined
+    };
+
+    const res = await GetCandlestickChart(params);
+
     let rawList = Array.isArray(res) ? res : (res && res.data ? res.data : []);
+
     if (rawList.length > 0) {
       const formattedData = rawList.map((item) => ({
-        timestamp: Number(item[0]), open: parseFloat(item[1]), high: parseFloat(item[2]),
-        low: parseFloat(item[3]), close: parseFloat(item[4]), volume: parseFloat(item[5])
+        timestamp: Number(item[0]),
+        open: parseFloat(item[1]),
+        high: parseFloat(item[2]),
+        low: parseFloat(item[3]),
+        close: parseFloat(item[4]),
+        volume: parseFloat(item[5])
       }));
+
+      // 保证按时间升序
       formattedData.sort((a, b) => a.timestamp - b.timestamp);
-      kLineData.value = formattedData;
 
-      if (marketInfo.value.price === "0.00") {
-        const lastBar = formattedData[formattedData.length - 1];
-        let totalVol = 0; formattedData.forEach(i => totalVol += i.volume);
-        const changeRate = formattedData[0].open ? ((lastBar.close - formattedData[0].open) / formattedData[0].open) * 100 : 0;
-        marketInfo.value = { price: lastBar.close, fiatPrice: lastBar.close, change: changeRate.toFixed(2), high: lastBar.high, low: lastBar.low, vol: totalVol, amount: totalVol * lastBar.close };
+      if (endTime) {
+        // --- 加载历史模式 ---
+        if (klineRef.value) {
+          const hasMore = formattedData.length >= 150;
+          // 核心点:子组件 KLineChart 内部现在会自动处理 overlap
+          klineRef.value.applyHistoryData(formattedData, hasMore);
+        }
+      } else {
+        // --- 初始化/重置模式 ---
+        kLineData.value = formattedData;
+        if (formattedData.length > 0) {
+           const lastBar = formattedData[formattedData.length - 1];
+           marketInfo.value = { ...marketInfo.value, price: lastBar.close, fiatPrice: lastBar.close, high: lastBar.high, low: lastBar.low };
+        }
+      }
+    } else {
+      // 没数据了
+      if (endTime && klineRef.value) {
+        klineRef.value.applyHistoryData([], false);
       }
     }
-  } catch (error) { console.error("API Error", error); }
+  } catch (error) {
+    console.error("API Error", error);
+    if (endTime && klineRef.value) {
+      // 发生错误时也要告知 KLineChart 停止加载,否则 loading 动画会一直转
+      klineRef.value.applyHistoryData([], false);
+    }
+  } finally {
+    isLoadingMore.value = false;
+  }
 };
 
+const handleLoadMore = (timestamp) => {
+  if (isLoadingMore.value) return;
+  isLoadingMore.value = true;
+  getKlineData(timestamp);
+};
+
+// --- WebSocket ---
 const connectWebSocket = () => {
   closeWebSocket();
   const symbolStr = String(route.query.type || "btcusdt").toLowerCase();
@@ -267,17 +298,25 @@ const updateKlineData = (newBar) => {
   if (!kLineData.value?.length) { kLineData.value = [newBar]; return; }
   const lastIndex = kLineData.value.length - 1;
   const lastBar = kLineData.value[lastIndex];
+  // 简单的 WebSocket 去重
   if (newBar.timestamp === lastBar.timestamp) kLineData.value.splice(lastIndex, 1, newBar);
   else if (newBar.timestamp > lastBar.timestamp) kLineData.value.push(newBar);
 };
 
-const handleVisibilityChange = () => { if (document.visibilityState === "visible" && socket.value?.readyState !== 1) connectWebSocket(); };
-onMounted(() => { isUnmounted = false; getKlineData(); connectWebSocket(); document.addEventListener("visibilitychange", handleVisibilityChange); });
+const handleVisibilityChange = () => {
+  if (document.visibilityState === "visible" && socket.value?.readyState !== 1)
+    connectWebSocket(); };
+onMounted(() => { isUnmounted = false;
+  getKlineData(); connectWebSocket();
+  document.addEventListener("visibilitychange", handleVisibilityChange); });
 onBeforeUnmount(() => { isUnmounted = true; closeWebSocket(); document.removeEventListener("visibilitychange", handleVisibilityChange); });
 watch(symbolId, () => { kLineData.value = []; getKlineData(); connectWebSocket(); });
-const messageChange = (key) => current.value = key;
 const formatNumber = (num) => num ? Number(num).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 6 }) : "0.00";
-const abbreviateNumber = (v) => { if (!v) return "0.00"; let n = parseFloat(v); if (n>=1e9) return (n/1e9).toFixed(2)+"B"; if (n>=1e6) return (n/1e6).toFixed(2)+"M"; if (n>=1e3) return (n/1e3).toFixed(2)+"K"; return n.toFixed(2); };
+const abbreviateNumber = (v) => { if (!v) return "0.00"; let n = parseFloat(v);
+  if (n>=1e9) return (n/1e9).toFixed(2)+"B";
+  if (n>=1e6) return (n/1e6).toFixed(2)+"M";
+  if (n>=1e3) return (n/1e3).toFixed(2)+"K";
+  return n.toFixed(2); };
 const getPricePrecision = (p) => (p < 1 ? 6 : p < 10 ? 4 : 2);
 const getPriceColor = (c) => (c >= 0 ? "fc45B26B" : "fcF6465D");
 const getUpDownClass = (c) => (c >= 0 ? "fc45B26B" : "fcF6465D");
@@ -287,50 +326,38 @@ const getUpDownClass = (c) => (c >= 0 ? "fc45B26B" : "fcF6465D");
 .fc45B26B { color: #2ebd85 !important; }
 .fcF6465D { color: #f6465d !important; }
 .fc1F2937 { color: #1f2937; }
-
-.time-tabs {
-  display: flex; align-items: center; justify-content: space-between;
-  width: 100%; box-sizing: border-box; padding: 0 15px 0 15px; position: relative; z-index: 20;
-}
-.tab-item {
-  font-size: 14px; color: #929aa5; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-weight: 500; display: flex; align-items: center; position: relative;
-}
+.time-tabs { display: flex; align-items: center; justify-content: space-between; width: 100%;
+  box-sizing: border-box; padding:5px 0 15px 15px; position: relative; z-index: 20; }
+.tab-item { font-size: 14px; color: #929aa5; padding: 4px 8px; border-radius: 4px; cursor: pointer;
+  font-weight: 500; display: flex; align-items: center; position: relative; }
 .tab-item.icon-btn { padding: 4px 4px; }
 .active-text { color: #fff; font-weight: 600; }
 .triangle { font-size: 8px; margin-left: 2px; transform: scale(0.9); }
 .tab-item img { display: block; height: 16px; width: auto; }
-
-.dropdown-menu {
-  position: absolute; top: 30px; left: -20px; width: 80px; background: #fff;
-  box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-radius: 6px; padding: 5px 0;
-  z-index: 100; display: flex; flex-direction: column;
-}
+.dropdown-menu { position: absolute; top: 30px; left: -20px; width: 80px; background: #fff;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-radius: 6px; padding: 5px 0; z-index: 100;
+  display: flex; flex-direction: column; }
 .drop-item { padding: 8px 15px; font-size: 13px; color: #666; text-align: center; }
 .drop-item.active { color: #F6465D; background: #fff5f5; }
-
-.indicator-panel {
-  position: absolute; top: 155px; left: 15px; right: 15px; background: #fff;
-  box-shadow: 0 4px 15px rgba(0,0,0,0.1); border-radius: 8px; padding: 15px; z-index: 99;
-}
+.indicator-panel { position: absolute; top: 155px; left: 15px; right: 15px; background: #fff;
+  box-shadow: 0 4px 15px rgba(0,0,0,0.1); border-radius: 8px; padding: 15px; z-index: 99; }
 .panel-section { margin-bottom: 15px; }
 .section-title { font-size: 12px; color: #999; margin-bottom: 8px; }
 .btn-group { display: flex; flex-wrap: wrap; gap: 10px; }
-.idx-btn { padding: 4px 12px; border: 1px solid #eee; border-radius: 14px; font-size: 12px; color: #666; cursor: pointer; }
+.idx-btn { padding: 4px 12px; border: 1px solid #eee; border-radius: 14px; font-size: 12px;
+  color: #666; cursor: pointer; }
 .idx-btn.active { border-color: #F6465D; color: #F6465D; background-color: #fff5f5; }
-
-.market-conditions { display: flex; flex-direction: column; align-items: center; margin-bottom: 50px; width: 100%; position: relative; }
-.market-price {
-  display: flex; justify-content: space-between; margin-top: 8px;
+.market-conditions { display: flex; flex-direction: column; align-items: center; width: 100%;
+  position: relative; min-height: 100vh; background: #fff; }
+.market-price { display: flex; justify-content: space-between; margin-top: 4px;
   width: 100%; padding: 0 15px; box-sizing: border-box;
-  .price-left { display: flex; flex-direction: column; width: 144px; line-height: 20px;
-    height: 69px;
-    .left-price { display: flex; align-items: center; height: 18px; }
-    .left-number { margin-top: 5px; }
-    .left-appro { display: flex; align-items: center;margin-top: 3px;
-      .appro { margin-left: 9px; } } }
-  .price-right { display: flex; flex-direction: column; height: 100%; line-height: 20px;
-    .right-number-top, .right-number-bottom { display: flex; justify-content: flex-end; width: 100%; height: 32px; .right-number-top-price, .right-number-top-number { margin-left: 10px; text-align: right; div { height: 16px; line-height: 16px; text-align: end; } } } .right-number-bottom {  } }
-}
+  .price-left { display: flex;line-height: 20px; flex-direction: column; width: 144px;
+    .left-price { height: 18px; line-height: 18px; } .left-number { margin-top: 5px; }
+    .left-appro { margin-top: 3px; .appro { margin-left: 9px; } } }
+  .price-right { display: flex; flex-direction: column; height: 100%;
+    .right-number-top, .right-number-bottom { display: flex; justify-content: flex-end; width: 100%; height: 32px; .right-number-top-price, .right-number-top-number { margin-left: 10px; text-align: right; div { height: 16px; line-height: 16px; text-align: end; } } } } }
 .k-line-main { height: 50vh; min-height: 350px; width: 100%; padding: 0 15px; }
-.notifi-classifi { display: flex; align-items: flex-end; margin-top: 15px; width: 100%; padding: 0 15px; box-sizing: border-box; height: 24px; border-bottom: 1px solid #f5f5f5; padding-bottom: 5px; .sys-notifi { margin-left: 47px; } }
+.notifi-classifi { display: flex; align-items: flex-end; margin-top: 140px;
+  width: 100%; padding: 0 15px 0 15px; box-sizing: border-box; height: 24px;
+  border-bottom: 1px solid #f5f5f5; .sys-notifi { margin-left: 47px; } }
 </style>

+ 14 - 7
src/views/user/LanguageSwitch.vue

@@ -1,14 +1,13 @@
 <template>
   <div class="page-container">
     <van-nav-bar
-      :title="$t('common.confirm')"
+      :title="t('common.switchLang')"
       left-arrow
       fixed
       placeholder
       @click-left="router.back()"
       class="custom-nav"
     />
-
     <div class="content-body">
       <div
         v-for="item in langList"
@@ -33,12 +32,13 @@
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, onMounted } from 'vue';
 import { useRouter } from 'vue-router';
 import { useI18n } from 'vue-i18n';
 import { showLoadingToast, closeToast, Icon as VanIcon } from 'vant';
-import { loadLanguageAsync } from '@/locales/index.js';
-
+// 🚨 路径修复:如果你的 index.js 在 src/locales/,则应该使用绝对路径 @/
+import { loadLanguageAsync } from '../../locales/index';
+const { t } = useI18n();
 const router = useRouter();
 const { locale } = useI18n();
 const currentLang = ref(locale.value);
@@ -65,14 +65,21 @@ const onSelectLang = async (item) => {
   });
 
   try {
-    await loadLanguageAsync(langCode);
-    currentLang.value = langCode;
+    // loadLanguageAsync 内部已经处理了 locale.value 的切换
+    const finalLang = await loadLanguageAsync(langCode);
+
+    // 确保组件状态与全局状态同步
+    locale.value = finalLang; // 这一行是必要的保险
+    currentLang.value = finalLang;
+
     closeToast();
+
     setTimeout(() => {
       // router.back();
       }, 200);
   } catch (error) {
     closeToast();
+    console.error('Language selection failed:', error);
   }
 };
 </script>

+ 2 - 1
vue.config.js

@@ -12,7 +12,8 @@ module.exports = defineConfig({
         // 如果后端在本地,可能是 http://localhost:8000
         // 如果是线上测试服,可能是 http://47.100.xx.xx
           //'http://63.141.230.43:57676',
-        target: 'http://backend.66linknow.com', // ✅ 必须加上协议
+          // 'http://backend.66linknow.com'
+        target:'http://backend.66linknow.com', // ✅ 必须加上协议
         changeOrigin: true, // 允许跨域
 
       },

Деякі файли не було показано, через те що забагато файлів було змінено