hookehuyr

feat(动画): 添加贝塞尔曲线动画路径组件

新增了一个基于SVG的贝塞尔曲线动画路径组件,支持逐步展示连接点之间的路径,并带有动画效果和箭头指示方向。同时,更新了路由配置以支持新页面,并添加了less依赖以支持样式预处理。
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
28 "@vueuse/core": "^13.0.0", 28 "@vueuse/core": "^13.0.0",
29 "autoprefixer": "^10.4.19", 29 "autoprefixer": "^10.4.19",
30 "axios": "^1.8.4", 30 "axios": "^1.8.4",
31 + "less": "^4.2.2",
31 "postcss": "^8.4.35", 32 "postcss": "^8.4.35",
32 "qs": "^6.14.0", 33 "qs": "^6.14.0",
33 "tailwindcss": "^3.4.1", 34 "tailwindcss": "^3.4.1",
...@@ -1858,6 +1859,18 @@ ...@@ -1858,6 +1859,18 @@
1858 "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 1859 "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1859 "dev": true 1860 "dev": true
1860 }, 1861 },
1862 + "node_modules/copy-anything": {
1863 + "version": "2.0.6",
1864 + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
1865 + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
1866 + "dev": true,
1867 + "dependencies": {
1868 + "is-what": "^3.14.1"
1869 + },
1870 + "funding": {
1871 + "url": "https://github.com/sponsors/mesqueeb"
1872 + }
1873 + },
1861 "node_modules/copy-text-to-clipboard": { 1874 "node_modules/copy-text-to-clipboard": {
1862 "version": "3.2.0", 1875 "version": "3.2.0",
1863 "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz", 1876 "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz",
...@@ -2001,6 +2014,19 @@ ...@@ -2001,6 +2014,19 @@
2001 "url": "https://github.com/fb55/entities?sponsor=1" 2014 "url": "https://github.com/fb55/entities?sponsor=1"
2002 } 2015 }
2003 }, 2016 },
2017 + "node_modules/errno": {
2018 + "version": "0.1.8",
2019 + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
2020 + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
2021 + "dev": true,
2022 + "optional": true,
2023 + "dependencies": {
2024 + "prr": "~1.0.1"
2025 + },
2026 + "bin": {
2027 + "errno": "cli.js"
2028 + }
2029 + },
2004 "node_modules/es-define-property": { 2030 "node_modules/es-define-property": {
2005 "version": "1.0.1", 2031 "version": "1.0.1",
2006 "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 2032 "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
...@@ -2362,6 +2388,13 @@ ...@@ -2362,6 +2388,13 @@
2362 "url": "https://github.com/sponsors/ljharb" 2388 "url": "https://github.com/sponsors/ljharb"
2363 } 2389 }
2364 }, 2390 },
2391 + "node_modules/graceful-fs": {
2392 + "version": "4.2.11",
2393 + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
2394 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
2395 + "dev": true,
2396 + "optional": true
2397 + },
2365 "node_modules/has-symbols": { 2398 "node_modules/has-symbols": {
2366 "version": "1.1.0", 2399 "version": "1.1.0",
2367 "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 2400 "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
...@@ -2401,6 +2434,32 @@ ...@@ -2401,6 +2434,32 @@
2401 "node": ">= 0.4" 2434 "node": ">= 0.4"
2402 } 2435 }
2403 }, 2436 },
2437 + "node_modules/iconv-lite": {
2438 + "version": "0.6.3",
2439 + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
2440 + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
2441 + "dev": true,
2442 + "optional": true,
2443 + "dependencies": {
2444 + "safer-buffer": ">= 2.1.2 < 3.0.0"
2445 + },
2446 + "engines": {
2447 + "node": ">=0.10.0"
2448 + }
2449 + },
2450 + "node_modules/image-size": {
2451 + "version": "0.5.5",
2452 + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
2453 + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
2454 + "dev": true,
2455 + "optional": true,
2456 + "bin": {
2457 + "image-size": "bin/image-size.js"
2458 + },
2459 + "engines": {
2460 + "node": ">=0.10.0"
2461 + }
2462 + },
2404 "node_modules/individual": { 2463 "node_modules/individual": {
2405 "version": "2.0.0", 2464 "version": "2.0.0",
2406 "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", 2465 "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz",
...@@ -2477,6 +2536,12 @@ ...@@ -2477,6 +2536,12 @@
2477 "node": ">=0.12.0" 2536 "node": ">=0.12.0"
2478 } 2537 }
2479 }, 2538 },
2539 + "node_modules/is-what": {
2540 + "version": "3.14.1",
2541 + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
2542 + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
2543 + "dev": true
2544 + },
2480 "node_modules/isexe": { 2545 "node_modules/isexe": {
2481 "version": "2.0.0", 2546 "version": "2.0.0",
2482 "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 2547 "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
...@@ -2542,6 +2607,32 @@ ...@@ -2542,6 +2607,32 @@
2542 "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz", 2607 "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz",
2543 "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==" 2608 "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg=="
2544 }, 2609 },
2610 + "node_modules/less": {
2611 + "version": "4.2.2",
2612 + "resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz",
2613 + "integrity": "sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==",
2614 + "dev": true,
2615 + "dependencies": {
2616 + "copy-anything": "^2.0.1",
2617 + "parse-node-version": "^1.0.1",
2618 + "tslib": "^2.3.0"
2619 + },
2620 + "bin": {
2621 + "lessc": "bin/lessc"
2622 + },
2623 + "engines": {
2624 + "node": ">=6"
2625 + },
2626 + "optionalDependencies": {
2627 + "errno": "^0.1.1",
2628 + "graceful-fs": "^4.1.2",
2629 + "image-size": "~0.5.0",
2630 + "make-dir": "^2.1.0",
2631 + "mime": "^1.4.1",
2632 + "needle": "^3.1.0",
2633 + "source-map": "~0.6.0"
2634 + }
2635 + },
2545 "node_modules/lilconfig": { 2636 "node_modules/lilconfig": {
2546 "version": "3.1.3", 2637 "version": "3.1.3",
2547 "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", 2638 "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
...@@ -2604,6 +2695,40 @@ ...@@ -2604,6 +2695,40 @@
2604 "@jridgewell/sourcemap-codec": "^1.5.0" 2695 "@jridgewell/sourcemap-codec": "^1.5.0"
2605 } 2696 }
2606 }, 2697 },
2698 + "node_modules/make-dir": {
2699 + "version": "2.1.0",
2700 + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
2701 + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
2702 + "dev": true,
2703 + "optional": true,
2704 + "dependencies": {
2705 + "pify": "^4.0.1",
2706 + "semver": "^5.6.0"
2707 + },
2708 + "engines": {
2709 + "node": ">=6"
2710 + }
2711 + },
2712 + "node_modules/make-dir/node_modules/pify": {
2713 + "version": "4.0.1",
2714 + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
2715 + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
2716 + "dev": true,
2717 + "optional": true,
2718 + "engines": {
2719 + "node": ">=6"
2720 + }
2721 + },
2722 + "node_modules/make-dir/node_modules/semver": {
2723 + "version": "5.7.2",
2724 + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
2725 + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
2726 + "dev": true,
2727 + "optional": true,
2728 + "bin": {
2729 + "semver": "bin/semver"
2730 + }
2731 + },
2607 "node_modules/math-intrinsics": { 2732 "node_modules/math-intrinsics": {
2608 "version": "1.1.0", 2733 "version": "1.1.0",
2609 "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 2734 "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
...@@ -2635,6 +2760,19 @@ ...@@ -2635,6 +2760,19 @@
2635 "node": ">=8.6" 2760 "node": ">=8.6"
2636 } 2761 }
2637 }, 2762 },
2763 + "node_modules/mime": {
2764 + "version": "1.6.0",
2765 + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
2766 + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
2767 + "dev": true,
2768 + "optional": true,
2769 + "bin": {
2770 + "mime": "cli.js"
2771 + },
2772 + "engines": {
2773 + "node": ">=4"
2774 + }
2775 + },
2638 "node_modules/mime-db": { 2776 "node_modules/mime-db": {
2639 "version": "1.52.0", 2777 "version": "1.52.0",
2640 "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 2778 "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
...@@ -2786,6 +2924,23 @@ ...@@ -2786,6 +2924,23 @@
2786 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2924 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
2787 } 2925 }
2788 }, 2926 },
2927 + "node_modules/needle": {
2928 + "version": "3.3.1",
2929 + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
2930 + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
2931 + "dev": true,
2932 + "optional": true,
2933 + "dependencies": {
2934 + "iconv-lite": "^0.6.3",
2935 + "sax": "^1.2.4"
2936 + },
2937 + "bin": {
2938 + "needle": "bin/needle"
2939 + },
2940 + "engines": {
2941 + "node": ">= 4.4.x"
2942 + }
2943 + },
2789 "node_modules/node-releases": { 2944 "node_modules/node-releases": {
2790 "version": "2.0.19", 2945 "version": "2.0.19",
2791 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", 2946 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
...@@ -2846,6 +3001,15 @@ ...@@ -2846,6 +3001,15 @@
2846 "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", 3001 "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
2847 "dev": true 3002 "dev": true
2848 }, 3003 },
3004 + "node_modules/parse-node-version": {
3005 + "version": "1.0.1",
3006 + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
3007 + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
3008 + "dev": true,
3009 + "engines": {
3010 + "node": ">= 0.10"
3011 + }
3012 + },
2849 "node_modules/path-key": { 3013 "node_modules/path-key": {
2850 "version": "3.1.1", 3014 "version": "3.1.1",
2851 "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 3015 "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
...@@ -3102,6 +3266,13 @@ ...@@ -3102,6 +3266,13 @@
3102 "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 3266 "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
3103 "dev": true 3267 "dev": true
3104 }, 3268 },
3269 + "node_modules/prr": {
3270 + "version": "1.0.1",
3271 + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
3272 + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
3273 + "dev": true,
3274 + "optional": true
3275 + },
3105 "node_modules/qs": { 3276 "node_modules/qs": {
3106 "version": "6.14.0", 3277 "version": "6.14.0",
3107 "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 3278 "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
...@@ -3286,6 +3457,20 @@ ...@@ -3286,6 +3457,20 @@
3286 "rust-result": "^1.0.0" 3457 "rust-result": "^1.0.0"
3287 } 3458 }
3288 }, 3459 },
3460 + "node_modules/safer-buffer": {
3461 + "version": "2.1.2",
3462 + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
3463 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
3464 + "dev": true,
3465 + "optional": true
3466 + },
3467 + "node_modules/sax": {
3468 + "version": "1.4.1",
3469 + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
3470 + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
3471 + "dev": true,
3472 + "optional": true
3473 + },
3289 "node_modules/scule": { 3474 "node_modules/scule": {
3290 "version": "1.3.0", 3475 "version": "1.3.0",
3291 "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", 3476 "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
...@@ -3406,6 +3591,16 @@ ...@@ -3406,6 +3591,16 @@
3406 "url": "https://github.com/sponsors/isaacs" 3591 "url": "https://github.com/sponsors/isaacs"
3407 } 3592 }
3408 }, 3593 },
3594 + "node_modules/source-map": {
3595 + "version": "0.6.1",
3596 + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
3597 + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
3598 + "dev": true,
3599 + "optional": true,
3600 + "engines": {
3601 + "node": ">=0.10.0"
3602 + }
3603 + },
3409 "node_modules/source-map-js": { 3604 "node_modules/source-map-js": {
3410 "version": "1.2.1", 3605 "version": "1.2.1",
3411 "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 3606 "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
...@@ -3703,6 +3898,12 @@ ...@@ -3703,6 +3898,12 @@
3703 "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", 3898 "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
3704 "dev": true 3899 "dev": true
3705 }, 3900 },
3901 + "node_modules/tslib": {
3902 + "version": "2.8.1",
3903 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
3904 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
3905 + "dev": true
3906 + },
3706 "node_modules/ufo": { 3907 "node_modules/ufo": {
3707 "version": "1.5.4", 3908 "version": "1.5.4",
3708 "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", 3909 "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
35 "@vueuse/core": "^13.0.0", 35 "@vueuse/core": "^13.0.0",
36 "autoprefixer": "^10.4.19", 36 "autoprefixer": "^10.4.19",
37 "axios": "^1.8.4", 37 "axios": "^1.8.4",
38 + "less": "^4.2.2",
38 "postcss": "^8.4.35", 39 "postcss": "^8.4.35",
39 "qs": "^6.14.0", 40 "qs": "^6.14.0",
40 "tailwindcss": "^3.4.1", 41 "tailwindcss": "^3.4.1",
......
1 /* 1 /*
2 * @Date: 2025-03-20 20:36:36 2 * @Date: 2025-03-20 20:36:36
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-03-25 15:17:18 4 + * @LastEditTime: 2025-03-28 09:39:27
5 * @FilePath: /mlaj/src/router/routes.js 5 * @FilePath: /mlaj/src/router/routes.js
6 * @Description: 路由地址映射配置 6 * @Description: 路由地址映射配置
7 */ 7 */
...@@ -175,6 +175,12 @@ export const routes = [ ...@@ -175,6 +175,12 @@ export const routes = [
175 meta: { title: 'test' }, 175 meta: { title: 'test' },
176 }, 176 },
177 { 177 {
178 + path: '/animation',
179 + name: 'animation',
180 + component: () => import('../views/animation.vue'),
181 + meta: { title: 'animation' },
182 + },
183 + {
178 path: '/upload_video', 184 path: '/upload_video',
179 name: 'upload_video', 185 name: 'upload_video',
180 component: () => import('../views/upload_video.vue'), 186 component: () => import('../views/upload_video.vue'),
......
1 +<!--
2 + * @Date: 2025-03-28 09:23:04
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-03-28 13:26:37
5 + * @FilePath: /mlaj/src/views/animation.vue
6 + * @Description: 贝塞尔曲线动画路径组件
7 + *
8 + * 该组件实现了一个基于SVG的贝塞尔曲线动画效果:
9 + * 1. 在画布上展示多个连接点
10 + * 2. 点击"下一步"按钮可以逐步显示连接点之间的贝塞尔曲线路径
11 + * 3. 路径带有动画效果和箭头指示方向
12 + * 4. 已激活的路径段会以绿色高亮显示
13 + * 5. 支持通过点击节点来激活路径
14 +-->
15 +<template>
16 + <div class="animation-container">
17 + <button class="next-button" @click="nextStep" :disabled="activeNodeIndex >= points.length - 1">下一步</button>
18 + <svg width="1000" height="1000" viewBox="0 0 1000 1000">
19 + <!-- 定义箭头标记 -->
20 + <defs>
21 + <marker
22 + id="arrow-inactive"
23 + viewBox="0 0 10 10"
24 + refX="5"
25 + refY="5"
26 + markerWidth="6"
27 + markerHeight="6"
28 + orient="auto-start-reverse"
29 + >
30 + <path d="M 0 0 L 10 5 L 0 10 z" fill="#ccc" />
31 + </marker>
32 + <marker
33 + id="arrow-active"
34 + viewBox="0 0 10 10"
35 + refX="5"
36 + refY="5"
37 + markerWidth="6"
38 + markerHeight="6"
39 + orient="auto-start-reverse"
40 + >
41 + <path d="M 0 0 L 10 5 L 0 10 z" fill="#4CAF50" />
42 + </marker>
43 + </defs>
44 +
45 + <!-- 贝塞尔曲线路径 -->
46 + <template v-for="(_, index) in points.slice(0, -1)" :key="index">
47 + <path
48 + v-show="activeNodeIndex === -1 || index > activeNodeIndex"
49 + :d="calculatePathSegment(points[index], points[index + 1])"
50 + fill="none"
51 + :stroke="pathColor"
52 + stroke-width="2"
53 + stroke-dasharray="5,5"
54 + class="path-animation"
55 + marker-end="url(#arrow-inactive)"
56 + />
57 + </template>
58 +
59 + <!-- 高亮的路径段 -->
60 + <path
61 + v-for="(segment, index) in activePathSegments"
62 + :key="index"
63 + :d="segment"
64 + fill="none"
65 + stroke="#4CAF50"
66 + stroke-width="2"
67 + stroke-dasharray="10"
68 + marker-end="url(#arrow-active)"
69 + class="active-path-animation"
70 + />
71 +
72 + <!-- 节点圆点 -->
73 + <template v-for="(point, index) in points" :key="index">
74 + <circle
75 + :cx="point.x"
76 + :cy="point.y"
77 + r="6"
78 + :class="['node', { 'node-active': activeNodeIndex >= index - 1 }]"
79 + @click="activateNode(index)"
80 + />
81 + </template>
82 + </svg>
83 + </div>
84 +</template>
85 +
86 +<script setup>
87 +import { ref, computed } from 'vue';
88 +
89 +// 定义节点坐标数组,每个节点包含x和y坐标
90 +// 这些点将用于生成贝塞尔曲线的路径
91 +const points = ref([
92 + { x: 250, y: 50 },
93 + { x: 10, y: 250 },
94 + { x: 400, y: 500 },
95 + { x: 50, y: 700 },
96 + { x: 400, y: 950 }
97 +]);
98 +
99 +// 当前激活的节点索引,初始值为-1表示没有节点被激活
100 +const activeNodeIndex = ref(-1);
101 +
102 +// 存储已激活的路径段数组,每个元素是一个SVG路径字符串
103 +// 用于显示高亮的贝塞尔曲线路径
104 +const activePathSegments = ref([]);
105 +
106 +// 计算完整的贝塞尔曲线路径
107 +// 使用三次贝塞尔曲线(Cubic Bezier)创建平滑的曲线效果
108 +// 控制点的位置通过当前点和下一个点的中点来计算
109 +const pathData = computed(() => {
110 + const path = [];
111 + points.value.forEach((point, index) => {
112 + if (index === 0) {
113 + path.push(`M ${point.x} ${point.y}`);
114 + } else {
115 + const prevPoint = points.value[index - 1];
116 + const segment = calculatePathSegment(prevPoint, point);
117 + path.push(segment.substring(segment.indexOf('C')));
118 + }
119 + });
120 + return path.join(' ');
121 +});
122 +
123 +// 计算两点之间的贝塞尔曲线路径段
124 +// @param startPoint - 起始点坐标 {x, y}
125 +// @param endPoint - 结束点坐标 {x, y}
126 +// @returns 返回SVG路径字符串
127 +const calculatePathSegment = (startPoint, endPoint) => {
128 + // 计算路径的方向向量
129 + const dx = endPoint.x - startPoint.x;
130 + const dy = endPoint.y - startPoint.y;
131 + const distance = Math.sqrt(dx * dx + dy * dy);
132 +
133 + // 设置节点间的偏移距离(可以根据需要调整)
134 + const offset = 0;
135 + const verticalOffset = -20; // 添加垂直偏移量参数
136 +
137 + // 计算单位向量
138 + const unitX = dx / distance;
139 + const unitY = dy / distance;
140 +
141 + // 计算偏移后的起点和终点,根据路径方向调整垂直偏移
142 + const adjustedStart = {
143 + x: startPoint.x + unitX * offset,
144 + y: startPoint.y + (dy > 0 ? -verticalOffset : verticalOffset)
145 + };
146 + const adjustedEnd = {
147 + x: endPoint.x - unitX * offset,
148 + y: endPoint.y + (dy > 0 ? verticalOffset : -verticalOffset)
149 + };
150 +
151 + // 计算控制点
152 + const cpx1 = adjustedStart.x;
153 + const cpy1 = adjustedStart.y + (adjustedEnd.y - adjustedStart.y) * 0.5;
154 + const cpx2 = adjustedEnd.x;
155 + const cpy2 = adjustedStart.y + (adjustedEnd.y - adjustedStart.y) * 0.5;
156 +
157 + return `M ${adjustedStart.x} ${adjustedStart.y} C ${cpx1} ${cpy1}, ${cpx2} ${cpy2}, ${adjustedEnd.x} ${adjustedEnd.y}`;
158 +};
159 +
160 +// 未激活路径的颜色
161 +// 使用浅灰色(#ccc)表示未激活状态
162 +const pathColor = computed(() => {
163 + return '#ccc';
164 +});
165 +
166 +
167 +// 处理节点点击事件,激活指定节点并更新路径
168 +// @param index - 被点击节点的索引
169 +// 点击节点时会激活该节点及其之前的所有节点和路径
170 +const activateNode = (index) => {
171 + activeNodeIndex.value = index;
172 + activePathSegments.value = [];
173 +
174 + // 重新计算所有激活的路径段
175 + for (let i = 0; i < index; i++) {
176 + const startPoint = points.value[i];
177 + const endPoint = points.value[i + 1];
178 + if (endPoint) {
179 + const segment = calculatePathSegment(startPoint, endPoint);
180 + activePathSegments.value.push(segment);
181 + }
182 + }
183 +};
184 +
185 +// 处理下一步按钮点击事件
186 +// 激活下一个节点并创建新的高亮路径段
187 +// 当到达最后一个节点时,按钮将被禁用
188 +const nextStep = () => {
189 + if (activeNodeIndex.value < points.value.length - 1) {
190 + activeNodeIndex.value++;
191 + const startPoint = points.value[activeNodeIndex.value];
192 + const endPoint = points.value[activeNodeIndex.value + 1];
193 + // 只有当存在下一个节点时才添加新的路径段
194 + if (endPoint) {
195 + const newSegment = calculatePathSegment(startPoint, endPoint);
196 + // 保留之前的路径段,添加新的路径段
197 + activePathSegments.value.push(newSegment);
198 + }
199 + // 如果是最后一个节点,直接将其设置为激活状态
200 + if (activeNodeIndex.value === points.value.length - 2) {
201 + activeNodeIndex.value = points.value.length - 1;
202 + }
203 + }
204 +};
205 +</script>
206 +
207 +<style scoped>
208 +.animation-container {
209 + display: flex;
210 + justify-content: center;
211 + align-items: center;
212 + min-height: 100vh;
213 + background: #f5f5f5;
214 +}
215 +
216 +.node {
217 + fill: white;
218 + stroke: #ccc;
219 + stroke-width: 2;
220 + cursor: pointer;
221 + transition: all 0.3s ease;
222 +}
223 +
224 +.node-active {
225 + fill: white;
226 + stroke: #4CAF50;
227 +}
228 +
229 +.path-animation {
230 + transition: all 0.5s ease;
231 +}
232 +
233 +.active-path-animation {
234 + animation: dash 1.5s linear infinite;
235 +}
236 +
237 +@keyframes dash {
238 + to {
239 + stroke-dashoffset: -20;
240 + }
241 +}
242 +
243 +.next-button {
244 + position: absolute;
245 + top: 20px;
246 + right: 20px;
247 + padding: 8px 16px;
248 + background-color: #4CAF50;
249 + color: white;
250 + border: none;
251 + border-radius: 4px;
252 + cursor: pointer;
253 + transition: all 0.3s ease;
254 +}
255 +
256 +.next-button:hover {
257 + background-color: #45a049;
258 +}
259 +
260 +.next-button:disabled {
261 + background-color: #cccccc;
262 + cursor: not-allowed;
263 +}
264 +</style>