Jelajahi Sumber

feat: 拖拽组件完善

kindring 10 bulan lalu
induk
melakukan
9303064b91

+ 45 - 53
package-lock.json

@@ -7,6 +7,9 @@
     "": {
       "name": "fc-ele",
       "version": "0.0.0",
+      "dependencies": {
+        "vuedraggable": "^4.1.0"
+      },
       "devDependencies": {
         "@types/better-sqlite3": "^7.6.10",
         "@types/express": "^4.17.21",
@@ -45,7 +48,6 @@
       "version": "7.22.14",
       "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.14.tgz",
       "integrity": "sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ==",
-      "dev": true,
       "bin": {
         "parser": "bin/babel-parser.js"
       },
@@ -782,8 +784,7 @@
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
-      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
-      "dev": true
+      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
     },
     "node_modules/@jridgewell/trace-mapping": {
       "version": "0.3.25",
@@ -1202,7 +1203,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
       "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==",
-      "dev": true,
       "dependencies": {
         "@babel/parser": "^7.21.3",
         "@vue/shared": "3.3.4",
@@ -1214,7 +1214,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz",
       "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==",
-      "dev": true,
       "dependencies": {
         "@vue/compiler-core": "3.3.4",
         "@vue/shared": "3.3.4"
@@ -1224,7 +1223,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
       "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
-      "dev": true,
       "dependencies": {
         "@babel/parser": "^7.20.15",
         "@vue/compiler-core": "3.3.4",
@@ -1242,7 +1240,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz",
       "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==",
-      "dev": true,
       "dependencies": {
         "@vue/compiler-dom": "3.3.4",
         "@vue/shared": "3.3.4"
@@ -1276,7 +1273,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz",
       "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==",
-      "dev": true,
       "dependencies": {
         "@vue/shared": "3.3.4"
       }
@@ -1285,7 +1281,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz",
       "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==",
-      "dev": true,
       "dependencies": {
         "@babel/parser": "^7.20.15",
         "@vue/compiler-core": "3.3.4",
@@ -1298,7 +1293,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz",
       "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==",
-      "dev": true,
       "dependencies": {
         "@vue/reactivity": "3.3.4",
         "@vue/shared": "3.3.4"
@@ -1308,7 +1302,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz",
       "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==",
-      "dev": true,
       "dependencies": {
         "@vue/runtime-core": "3.3.4",
         "@vue/shared": "3.3.4",
@@ -1319,7 +1312,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
       "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
-      "dev": true,
       "dependencies": {
         "@vue/compiler-ssr": "3.3.4",
         "@vue/shared": "3.3.4"
@@ -1331,8 +1323,7 @@
     "node_modules/@vue/shared": {
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
-      "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==",
-      "dev": true
+      "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
     },
     "node_modules/@vue/typescript": {
       "version": "1.8.8",
@@ -2636,8 +2627,7 @@
     "node_modules/csstype": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
-      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
-      "dev": true
+      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
     },
     "node_modules/date-format": {
       "version": "4.0.14",
@@ -3316,8 +3306,7 @@
     "node_modules/estree-walker": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
-      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
-      "dev": true
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
     "node_modules/etag": {
       "version": "1.8.1",
@@ -4736,7 +4725,6 @@
       "version": "0.30.3",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz",
       "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==",
-      "dev": true,
       "dependencies": {
         "@jridgewell/sourcemap-codec": "^1.4.15"
       },
@@ -5130,7 +5118,6 @@
       "version": "3.3.7",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
       "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -5527,8 +5514,7 @@
     "node_modules/picocolors": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
-      "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
-      "dev": true
+      "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
     },
     "node_modules/picomatch": {
       "version": "2.3.1",
@@ -5578,7 +5564,6 @@
       "version": "8.4.38",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
       "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
-      "dev": true,
       "funding": [
         {
           "type": "opencollective",
@@ -6508,6 +6493,11 @@
         "node": ">= 10"
       }
     },
+    "node_modules/sortablejs": {
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
+      "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
+    },
     "node_modules/source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -6521,7 +6511,6 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
       "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -7271,7 +7260,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz",
       "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==",
-      "dev": true,
       "dependencies": {
         "@vue/compiler-dom": "3.3.4",
         "@vue/compiler-sfc": "3.3.4",
@@ -7322,6 +7310,17 @@
         "node": ">=10"
       }
     },
+    "node_modules/vuedraggable": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
+      "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
+      "dependencies": {
+        "sortablejs": "1.14.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.1"
+      }
+    },
     "node_modules/wcwidth": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
@@ -7517,8 +7516,7 @@
     "@babel/parser": {
       "version": "7.22.14",
       "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.14.tgz",
-      "integrity": "sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ==",
-      "dev": true
+      "integrity": "sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ=="
     },
     "@develar/schema-utils": {
       "version": "2.6.5",
@@ -7963,8 +7961,7 @@
     "@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
-      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
-      "dev": true
+      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
     },
     "@jridgewell/trace-mapping": {
       "version": "0.3.25",
@@ -8325,7 +8322,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
       "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==",
-      "dev": true,
       "requires": {
         "@babel/parser": "^7.21.3",
         "@vue/shared": "3.3.4",
@@ -8337,7 +8333,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz",
       "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==",
-      "dev": true,
       "requires": {
         "@vue/compiler-core": "3.3.4",
         "@vue/shared": "3.3.4"
@@ -8347,7 +8342,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
       "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
-      "dev": true,
       "requires": {
         "@babel/parser": "^7.20.15",
         "@vue/compiler-core": "3.3.4",
@@ -8365,7 +8359,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz",
       "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==",
-      "dev": true,
       "requires": {
         "@vue/compiler-dom": "3.3.4",
         "@vue/shared": "3.3.4"
@@ -8391,7 +8384,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz",
       "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==",
-      "dev": true,
       "requires": {
         "@vue/shared": "3.3.4"
       }
@@ -8400,7 +8392,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz",
       "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==",
-      "dev": true,
       "requires": {
         "@babel/parser": "^7.20.15",
         "@vue/compiler-core": "3.3.4",
@@ -8413,7 +8404,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz",
       "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==",
-      "dev": true,
       "requires": {
         "@vue/reactivity": "3.3.4",
         "@vue/shared": "3.3.4"
@@ -8423,7 +8413,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz",
       "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==",
-      "dev": true,
       "requires": {
         "@vue/runtime-core": "3.3.4",
         "@vue/shared": "3.3.4",
@@ -8434,7 +8423,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
       "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
-      "dev": true,
       "requires": {
         "@vue/compiler-ssr": "3.3.4",
         "@vue/shared": "3.3.4"
@@ -8443,8 +8431,7 @@
     "@vue/shared": {
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
-      "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==",
-      "dev": true
+      "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
     },
     "@vue/typescript": {
       "version": "1.8.8",
@@ -9423,8 +9410,7 @@
     "csstype": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
-      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
-      "dev": true
+      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
     },
     "date-format": {
       "version": "4.0.14",
@@ -9953,8 +9939,7 @@
     "estree-walker": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
-      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
-      "dev": true
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
     "etag": {
       "version": "1.8.1",
@@ -11054,7 +11039,6 @@
       "version": "0.30.3",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz",
       "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==",
-      "dev": true,
       "requires": {
         "@jridgewell/sourcemap-codec": "^1.4.15"
       }
@@ -11360,8 +11344,7 @@
     "nanoid": {
       "version": "3.3.7",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
-      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
-      "dev": true
+      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
     },
     "napi-build-utils": {
       "version": "1.0.2",
@@ -11644,8 +11627,7 @@
     "picocolors": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
-      "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
-      "dev": true
+      "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
     },
     "picomatch": {
       "version": "2.3.1",
@@ -11680,7 +11662,6 @@
       "version": "8.4.38",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
       "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
-      "dev": true,
       "requires": {
         "nanoid": "^3.3.7",
         "picocolors": "^1.0.0",
@@ -12318,6 +12299,11 @@
         "socks": "^2.6.2"
       }
     },
+    "sortablejs": {
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
+      "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
+    },
     "source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -12327,8 +12313,7 @@
     "source-map-js": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
-      "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
-      "dev": true
+      "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg=="
     },
     "source-map-support": {
       "version": "0.5.21",
@@ -12868,7 +12853,6 @@
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz",
       "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==",
-      "dev": true,
       "requires": {
         "@vue/compiler-dom": "3.3.4",
         "@vue/compiler-sfc": "3.3.4",
@@ -12909,6 +12893,14 @@
         }
       }
     },
+    "vuedraggable": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
+      "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
+      "requires": {
+        "sortablejs": "1.14.0"
+      }
+    },
     "wcwidth": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",

+ 3 - 0
package.json

@@ -29,5 +29,8 @@
     "vite-plugin-optimizer": "^1.4.2",
     "vue": "^3.3.4",
     "vue-tsc": "^1.8.5"
+  },
+  "dependencies": {
+    "vuedraggable": "^4.1.0"
   }
 }

+ 20 - 2
src/App.vue

@@ -5,6 +5,7 @@ import {onMounted, ref} from "vue";
 import MacWindow from "./components/window/macWindow.vue";
 import MagnetView from "./components/magnetView.vue";
 import AppleBar from "@/components/appleBar/appleBar.vue";
+import BarIconBtn from "@/components/appleBar/barIconBtn.vue";
 import SettingView from "@/components/settingView.vue";
 import {NavItem} from "@/components/appleBar/appleBar.ts";
 
@@ -37,7 +38,15 @@ let navItems:NavItem[] = [
 
 const title = ref("fc-ele");
 const pageKey = ref(homePageKey);
+const editMode = ref(false);
+function editModeChange() {
+  editMode.value = !editMode.value;
+}
+
 const navAction = (actionCode:string) => {
+  if(editMode){
+    return console.log('is edit mode')
+  }
   console.log(`action: ${actionCode}`);
   // 寻找actionCode对应的 index
   let index = navItems.findIndex((item) => item.actionCode === actionCode);
@@ -75,7 +84,9 @@ const navAction = (actionCode:string) => {
     <Transition :name="transitionName">
       <div class="full" v-if="pageKey === homePageKey">
         <div class="app-content">
-          <magnet-view/>
+          <magnet-view
+              :edit-mode="editMode"
+          />
         </div>
       </div>
       <setting-view v-else-if="pageKey === settingPageKey"></setting-view>
@@ -87,7 +98,14 @@ const navAction = (actionCode:string) => {
           :active="pageKey"
           :hide-time="3000"
           @action="navAction"
-      ></apple-bar>
+      >
+        <bar-icon-btn
+            icon-name="edit"
+            :active="editMode"
+            @click.native="editModeChange"
+        >
+        </bar-icon-btn>
+      </apple-bar>
     </div>
 
   </mac-window>

+ 1 - 0
src/assets/svg/add.svg

@@ -0,0 +1 @@
+<svg t="1717038371286" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3431" width="200" height="200"><path d="M972.8 512c0 11.264-9.216 20.48-20.48 20.48H532.48v419.84c0 11.264-9.216 20.48-20.48 20.48s-20.48-9.216-20.48-20.48V532.48H71.68c-11.264 0-20.48-9.216-20.48-20.48s9.216-20.48 20.48-20.48h419.84V71.68c0-11.264 9.216-20.48 20.48-20.48s20.48 9.216 20.48 20.48v419.84h419.84c11.264 0 20.48 9.216 20.48 20.48z" p-id="3432"></path></svg>

+ 1 - 0
src/assets/svg/edit.svg

@@ -0,0 +1 @@
+<svg t="1717038595817" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4423" width="200" height="200"><path d="M853.333333 501.333333c-17.066667 0-32 14.933333-32 32v320c0 6.4-4.266667 10.666667-10.666666 10.666667H170.666667c-6.4 0-10.666667-4.266667-10.666667-10.666667V213.333333c0-6.4 4.266667-10.666667 10.666667-10.666666h320c17.066667 0 32-14.933333 32-32s-14.933333-32-32-32H170.666667c-40.533333 0-74.666667 34.133333-74.666667 74.666666v640c0 40.533333 34.133333 74.666667 74.666667 74.666667h640c40.533333 0 74.666667-34.133333 74.666666-74.666667V533.333333c0-17.066667-14.933333-32-32-32z"   p-id="4424"></path><path d="M405.333333 484.266667l-32 125.866666c-2.133333 10.666667 0 23.466667 8.533334 29.866667 6.4 6.4 14.933333 8.533333 23.466666 8.533333h8.533334l125.866666-32c6.4-2.133333 10.666667-4.266667 14.933334-8.533333l300.8-300.8c38.4-38.4 38.4-102.4 0-140.8-38.4-38.4-102.4-38.4-140.8 0L413.866667 469.333333c-4.266667 4.266667-6.4 8.533333-8.533334 14.933334z m59.733334 23.466666L761.6 213.333333c12.8-12.8 36.266667-12.8 49.066667 0 12.8 12.8 12.8 36.266667 0 49.066667L516.266667 558.933333l-66.133334 17.066667 14.933334-68.266667z"   p-id="4425"></path></svg>

+ 18 - 71
src/components/appleBar/appleBar.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 import {ref, onMounted} from 'vue'
 import type{PropType} from 'vue'
+import barIconBtn from './barIconBtn.vue'
 import type{NavItem} from './appleBar.ts'
 
 let hideTimer:NodeJS.Timeout;
@@ -75,24 +76,23 @@ function actionHandle(actionCode: string): void{
     <div class="appleBar" @mouseenter="barActiveHandle" @mouseleave="startHideTimer">
       <div class="bgMask"></div>
       <div class="appleItemGroup">
-        <div
-            :class="`appleItem showCenterTip ${active==item.actionCode?'active':''}`"
-             v-for="item in props.navItems"
-             :key="item.id"
-              @click="actionHandle(item.actionCode)"
-        >
-          <div class="appleItem-content">
-            <div class="showTip">{{item.description}}</div>
-            <icon-svg class="icon" :icon-name="item.icon"></icon-svg>
-          </div>
-        </div>
+        <slot name="left"></slot>
       </div>
-<!--      右键-->
-      <div class="appleItem appleUser" @click="actionHandle('user')">
-        <div class="appleItem-content">
-          <icon-svg class="icon" icon-name="user"></icon-svg>
-        </div>
+      <div class="appleItemGroup">
+        <bar-icon-btn
+            v-for="item in props.navItems"
+            :key="item.id"
+            :active="active==item.actionCode"
+            :description="item.description"
+            :icon-name="item.icon"
+            @click.native="actionHandle(item.actionCode)"
+        ></bar-icon-btn>
+      </div>
+<!--      右侧-->
+      <div class="appleItemGroup">
+        <slot></slot>
       </div>
+
     </div>
   </div>
 </template>
@@ -130,67 +130,14 @@ function actionHandle(actionCode: string): void{
   background-color: var(--color-background-soft);
 }
 .appleItemGroup{
-  width: 100%;
+  width: auto;
   height: 100%;
   display: flex;
   justify-content: center;
   align-items: center;
   position: relative;
 }
-.appleItem{
-  width: 60px;
-  height: 60px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  margin: 0 10px;
-  font-size: 20px;
-  font-weight: bold;
-  color: var(--color-text);
-  cursor: pointer;
-  opacity: 0.8;
-}
-.appleItemGroup .active{
-  color: var(--color-text-show);
-  transform: scale(1.25);;
-}
-.appleItem-content{
-  width: 45px;
-  height: 45px;
-  border-radius: 50%;
-  box-shadow: 0 0 3px 0 var(--color-text);
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  transition: all 0.8s;
-  background-color: var(--color-background-soft);
-}
-
-.appleUser{
-  width: 45px;
-  height: 45px;
-  display: flex;
-  flex-shrink: 0;
-  position: relative;
-}
-
-.appleItem .icon{
-  height: 100%;
-  font-size: 1.5em;
-  color: var(--color-text);
-  transition: all 0.8s;
-}
-.appleItemGroup .active .icon{
-  color: var(--color-text-show);
-}
 
-.appleItem:hover .appleItem-content, .appleUser:hover .appleItem-content{
-  transform: scale(1.35);
-  box-shadow: 0 0 10px 0 rgba(0,0,0,0.2);
-  color: var(--color-text);
-  background-color: var(--color-background-soft);
-  transition: all 0.8s;
-}
 
 
 .hidden .appleBar{
@@ -203,7 +150,7 @@ function actionHandle(actionCode: string): void{
   background-color: var(--color-background);
 }
 
-.hidden .appleBar .appleItem{
+.hidden .appleBar > *{
   display: none;
 }
 

+ 78 - 0
src/components/appleBar/barIconBtn.vue

@@ -0,0 +1,78 @@
+<script setup lang="ts">
+defineProps({
+  iconName: {
+    type: String,
+  },
+  description: {
+    type: String,
+    default: '',
+  },
+  active: {
+    type: Boolean,
+    default: false,
+  }
+})
+</script>
+
+<template>
+  <div class="appleItem" >
+    <div :class="`appleItem showCenterTip ${active?'active':''}`">
+      <div class="appleItem-content">
+        <div v-if="description" class="showTip">{{description}}</div>
+        <icon-svg class="icon" :icon-name="iconName"></icon-svg>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+
+.appleItem{
+  width: 60px;
+  height: 60px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin: 0 10px;
+  font-size: 20px;
+  font-weight: bold;
+  color: var(--color-text);
+  cursor: pointer;
+  opacity: 0.8;
+}
+.appleItemGroup .active{
+  color: var(--color-text-show);
+  transform: scale(1.25);;
+}
+.appleItem-content{
+  width: 45px;
+  height: 45px;
+  border-radius: 50%;
+  box-shadow: 0 0 3px 0 var(--color-text);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  transition: all 0.8s;
+  background-color: var(--color-background-soft);
+}
+
+.appleItem .icon{
+  height: 100%;
+  font-size: 1.5em;
+  color: var(--color-text);
+  transition: all 0.8s;
+}
+.appleItemGroup .active .icon{
+  color: var(--color-text-show);
+}
+
+.appleItem:hover .appleItem-content, .appleUser:hover .appleItem-content{
+  transform: scale(1.35);
+  box-shadow: 0 0 10px 0 rgba(0,0,0,0.2);
+  color: var(--color-text);
+  background-color: var(--color-background-soft);
+  transition: all 0.8s;
+}
+
+
+</style>

+ 22 - 14
src/components/magnetView.vue

@@ -1,9 +1,10 @@
 <script setup lang="ts">
 import { shallowRef} from "vue";
+import TimeMagnet from "@/components/magnets/timeMagnet.vue";
+import vueDrag from "@/components/public/vueDrag.vue";
 import {Magnet, MagnetEmit, MagnetSize} from "@/types/magnetType.ts";
 import {computeMagnetStyle, initTimeMagnetInfo} from "@/components/magnets/magnetInfo.ts";
 
-import TimeMagnet from "@/components/magnets/timeMagnet.vue";
 import {Calendar} from "@/util/time.ts";
 
 const timeMagnetInfo = initTimeMagnetInfo(TimeMagnet)
@@ -56,27 +57,35 @@ function eventHandler(magnetEmit: MagnetEmit<any>){
   }
 }
 
+defineProps({
+  editMode: {
+    type: Boolean,
+    default: false
+  }
+})
+
 
 
 </script>
 
 <template>
-<!-- 磁帖 布局组件, -->
-<div class="magnet scroll">
-  <!--    磁贴组件布局 -->
-  <div class="magnet-item"
+  <!-- 磁帖 布局组件, -->
+  <div class="magnet scroll">
+    <!--    磁贴组件布局 -->
+
+    <vue-drag class="magnet-item"
        v-for="magnet in magnetItems"
        :key="magnet.id"
        :style="computeMagnetStyle(magnet)"
-  >
-    <div class="magnet-item-bg"></div>
-    <component
-        :is="magnet.component"
-        :size="magnet.size"
-        @magnet="eventHandler"
-    ></component>
+       :open-drag="editMode"
+    >
+      <component
+          :is="magnet.component"
+          :size="magnet.size"
+          @magnet="eventHandler"
+      ></component>
+    </vue-drag>
   </div>
-</div>
 </template>
 
 <style scoped>
@@ -86,7 +95,6 @@ function eventHandler(magnetEmit: MagnetEmit<any>){
   display: flex;
   position: relative;
   overflow-y: auto;
-  padding-bottom: 60px;
 }
 
 

+ 136 - 0
src/components/public/vueDrag.vue

@@ -0,0 +1,136 @@
+<script setup lang="ts">
+import {ref, watch} from 'vue'
+import {Drag, MouseInfo, ElementInfo} from '@/util/domDrag.ts'
+const dragRef = ref<HTMLElement>()
+// init drag
+
+const props = defineProps({
+  openDrag: {
+    type: Boolean,
+    default: false
+  },
+  // 移动时隐藏原有dom
+  moveHide: {
+    type: Boolean,
+    default: false
+  }
+})
+let drag: Drag | null = null
+
+function initDrag() {
+  console.log(` start init drag`)
+  let dragEle = dragRef.value
+  if (!dragEle) return console.error('dragElement is null !!!!!!!!!! ')
+  if(!drag){
+    drag = new Drag(dragEle)
+  }
+  drag.on(Drag.Event.moveStart, moveStartHandle)
+  drag.on(Drag.Event.move, moveHandle)
+  drag.on(Drag.Event.moveEnd, moveEndHandle)
+}
+function unDrag(){
+  if (!drag) return console.error('dragElement is null !!!!!!!!!! ')
+  drag.off(Drag.Event.moveStart)
+  drag.off(Drag.Event.move)
+  drag.off(Drag.Event.moveEnd)
+}
+
+let moveStyle = ref({})
+moveStyle.value = {
+  position: "absolute",
+  left: `100px` ,
+  top: `10px`
+}
+
+let elementDisplayValue = ""
+let parentPositionValue = ""
+// 直接添加至父元素中的临时拷贝dom
+let templateElement = null;
+
+function moveStartHandle(mouseInfo: MouseInfo, dragInfo: ElementInfo){
+  console.log('开始移动')
+  console.log(mouseInfo)
+  console.log(dragInfo)
+  // 存储当前元素的样式
+  let el = dragInfo.el;
+  if(!el){
+    return console.log("未知的dom元素")
+  }
+  let parentEl = el.offsetParent;
+  templateElement = el.cloneNode(true);
+  // 移除克隆的dom元素的所有事件
+  templateElement.style.position = "absolute";
+  templateElement.style.left = `${mouseInfo.x - dragInfo.diffX -  dragInfo.parentLeft}px`;
+  templateElement.style.top = `${mouseInfo.y - dragInfo.diffY -  dragInfo.parentTop}px`;
+  // 如果父元素无定位根基属性
+  parentPositionValue = parentEl.style.position;
+  if(parentPositionValue != "absolute" && parentPositionValue != "relative" && parentPositionValue != "fixed" )
+  {
+    parentEl.style.position = "relative"
+  }
+  parentEl.append(templateElement)
+  if(props.moveHide){
+    elementDisplayValue = el.style.display
+    // 获取原有的 opacity 和 display 信息
+    el.style.display = "none"
+  }
+  return true;
+}
+
+function moveHandle(mouseInfo: MouseInfo, dragInfo: ElementInfo){
+  console.log('move')
+  console.log(mouseInfo)
+  console.log(dragInfo)
+  templateElement.style.position = "absolute";
+  templateElement.style.left = `${mouseInfo.x - dragInfo.diffX - dragInfo.parentLeft}px`;
+  templateElement.style.top = `${mouseInfo.y - dragInfo.diffY - dragInfo.parentTop}px`;
+}
+
+function moveEndHandle(mouseInfo: MouseInfo, dragInfo: ElementInfo){
+  console.log('结束移动')
+  console.log(mouseInfo)
+  console.log(dragInfo)
+  let el = dragInfo.el;
+  if(!el){
+    return console.log("未知的dom元素")
+  }
+  let parentEl = el.offsetParent;
+  if(props.moveHide){
+    // 获取原有的 opacity 和 display 信息
+    el.style.display = elementDisplayValue
+    elementDisplayValue = ""
+  }
+  // 移除临时dom元素
+  templateElement.remove()
+  moveStyle.value = {}
+  if(parentPositionValue != "absolute" && parentPositionValue != "relative" && parentPositionValue != "fixed" )
+  {
+    parentEl.style.position = parentPositionValue
+  }
+}
+
+
+// watch openDrag
+watch(()=>props.openDrag, (val) => {
+  if (val){
+    initDrag()
+  }else {
+    unDrag()
+  }
+
+})
+
+
+
+
+</script>
+
+<template>
+  <div class="drag-box" ref="dragRef" >
+    <slot></slot>
+  </div>
+
+</template>
+
+<style scoped>
+</style>

+ 5 - 2
src/main/tools/port.ts

@@ -13,12 +13,14 @@ export async function portIsOccupied(port: number) : Promise<number> {
             resolve(port);
         })
 
-        server.on('error', (err: Error) => {
-            if (err.name === 'EADDRINUSE') {
+        server.on('error', (err: any) => {
+            if (err.code === 'EADDRINUSE') {
                 // 端口已经被使用
                 resolve(-1);
             } else {
                 // 未知异常
+                console.log(err)
+                console.log(err.name)
                 reject(err)
             }
         });
@@ -46,6 +48,7 @@ getAvailablePort<T>(port: number = 3000, maxTry: number = 100): Promise<[Error |
             }
             rPort += 1;
             tryCount += 1;
+            console.log(rPort)
             if( maxTry >= 0 && tryCount >= maxTry && rPort > 65535){
                 return [null,-1];
             }

+ 3 - 0
src/style.css

@@ -80,6 +80,9 @@
   /*box-size: border-box;*/
   margin: 0;
   padding: 0;
+  /* 禁止 拖拽元素 */
+  -webkit-user-drag: none;
+  user-select:none;
 }
 html , body{
   font-size: 16px;

+ 268 - 0
src/util/domDrag.ts

@@ -0,0 +1,268 @@
+// 元素拖动
+function getElementLeft(el: HTMLElement): number{
+    let left = el.offsetLeft || 0;
+    if(el.parentElement){
+        left += getElementLeft(el.parentElement);
+    }
+    return left;
+}
+function getElementTop(el: HTMLElement): number{
+    let top = el.offsetTop || 0;
+    if(el.parentElement){
+        top += getElementTop(el.parentElement);
+    }
+    return top;
+}
+
+function getElementDistanceToViewportEdge(element: HTMLElement): { top: number, right: number, bottom: number, left: number } {
+    const rect = element.getBoundingClientRect();
+    const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
+    const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
+    const scrollX = window.scrollX || window.pageXOffset || document.documentElement.scrollLeft;
+    const scrollY = window.scrollY || window.pageYOffset || document.documentElement.scrollTop;
+
+    const top = rect.top - scrollY;
+    const right = viewportWidth - rect.right + scrollX;
+    const bottom = viewportHeight - rect.bottom + scrollY;
+    const left = rect.left - scrollX;
+
+    return { top, right, bottom, left };
+}
+
+
+
+export interface ElementInfo{
+    el: HTMLElement | null;
+    left: number;
+    top: number;
+    parentLeft: number;
+    parentTop: number;
+    width: number;
+    height: number;
+    // 鼠标点击位置与元素位置的差值
+    diffX: number;
+    diffY: number;
+
+}
+
+export interface MouseInfo{
+    x: number;
+    y: number;
+}
+
+export interface MouseListener{
+    (mouse: MouseInfo, el: ElementInfo): void;
+}
+export class Drag{
+    constructor(el: HTMLElement, moveWait: number = 60) {
+        // 绑定事件
+        this.el = el;
+        this.moveWait = moveWait;
+        this.bindEvent();
+    }
+    el: HTMLElement;
+    isMove: boolean = false;
+    // 延迟时间, 同一时间内的合并为同一
+    moveWait: number;
+    // 计时器
+    waitTimer: NodeJS.Timeout | null = null ;
+    parent: ElementInfo = {
+        el: null,
+        left: 0,
+        top: 0,
+        parentTop: 0,
+        parentLeft: 0,
+        width: 0,
+        height: 0,
+        diffX: 0,
+        diffY: 0
+    };
+    thisInfo: ElementInfo = {
+        el: null,
+        left: 0,
+        top: 0,
+        parentTop: 0,
+        parentLeft: 0,
+        width: 0,
+        height: 0,
+        diffX: 0,
+        diffY: 0
+    };
+    startX: number = 0;
+    startY: number = 0;
+
+    public static Event = {
+        moveStart: "moveStart",
+        move: "move",
+        moveEnd: "mouseEnd"
+    }
+
+
+    //
+    private bindEvent() {
+        const el = this.el;
+        el.addEventListener('mousedown', this.downEvent);
+        let parent = el.parentElement;
+        if (!parent) {
+            parent = document.body;
+        }
+        const parentDistance = getElementDistanceToViewportEdge(parent);
+        const distance = getElementDistanceToViewportEdge(el);
+        this.parent = {
+            el: parent,
+            left: parentDistance.left,
+            top: parentDistance.top,
+            parentTop: 0,
+            parentLeft: 0,
+            width: parent.offsetWidth,
+            height: parent.offsetHeight,
+            diffX: 0,
+            diffY: 0
+        }
+        this.thisInfo = {
+            el: el,
+            left: getElementLeft(el),
+            top: getElementTop(el),
+            parentTop: this.parent.top,
+            parentLeft: this.parent.left,
+            width: el.offsetWidth,
+            height: el.offsetHeight,
+            diffX: 0,
+            diffY: 0
+        }
+
+
+        console.log('Distance to top:', distance.top);
+        console.log('Distance to right:', distance.right);
+        console.log('Distance to bottom:', distance.bottom);
+        console.log('Distance to left:', distance.left);
+    }
+    downEvent = (e: MouseEvent) => {
+        if(this.isMove){
+            return
+        }
+        // 获取鼠标位置
+        const x = e.clientX;
+        const y = e.clientY;
+        let el = this.el;
+        this.isMove = true;
+
+        console.log(el);
+        // 获取元素再页面上的位置
+        const left = getElementLeft(el);
+        const top = getElementTop(el);
+        let parentLeft = 0;
+        let parentTop = 0;
+
+
+        console.log(left, top)
+        console.log(parentLeft, parentTop)
+
+        // 计算元素偏移差值
+        const diffX = e.offsetX;
+        const diffY =  e.offsetY;
+
+        this.startX = x;
+        this.startY = y;
+
+        this.thisInfo = {
+            ...this.thisInfo,
+            diffX: diffX,
+            diffY: diffY
+        }
+        if (this.parent.el) {
+            const parentDistance = getElementDistanceToViewportEdge(this.parent.el)
+            this.thisInfo.parentTop = parentDistance.top
+            this.thisInfo.parentLeft = parentDistance.left
+        }
+        let mouseInfo: MouseInfo = {
+            x: x,
+            y: y
+        }
+        this.moveStart && this.moveStart(mouseInfo, this.thisInfo);
+        setTimeout(()=>{
+            document.addEventListener('mousemove', this.mouseMove);
+        }, 100)
+        document.addEventListener('mouseup', this.mouseUp);
+
+    }
+
+    mouseUp = (e: MouseEvent) => {
+        this.isMove = false;
+        // 获取鼠标位置
+        const x = e.clientX;
+        const y = e.clientY;
+        console.log(`mouse up event x: ${x} y: ${y}`)
+        let mouseInfo: MouseInfo = {
+            x: x,
+            y: y
+        }
+        // 解除事件绑定
+        document.removeEventListener('mousemove', this.mouseMove);
+        document.removeEventListener('mouseup', this.mouseUp);
+        this.moveEnd && this.moveEnd(mouseInfo , this.thisInfo);
+    }
+    mouseMove = (e: MouseEvent) => {
+        if (!this.isMove){
+            // 已经被取消
+            document.removeEventListener('mousemove', this.mouseMove);
+            return
+        }
+        // 获取鼠标位置
+        const x = e.clientX;
+        const y = e.clientY;
+        console.log(`mouse move event x: ${x} y: ${y}`)
+        let mouseInfo: MouseInfo = {
+            x: x,
+            y: y
+        }
+        this.move && this.move(mouseInfo, this.thisInfo);
+    }
+
+    moveStart: MouseListener | null = null;
+    move: MouseListener | null  = null;
+    moveEnd: MouseListener | null  = null;
+
+    public on(eventName: string, callback: MouseListener) {
+        switch(eventName) {
+            case Drag.Event.moveStart:
+                this.moveStart = callback;
+                break;
+            case Drag.Event.move:
+                this.move = callback;
+                break;
+            case Drag.Event.moveEnd:
+                this.moveEnd = callback;
+                break;
+            default:
+                throw new Error('Invalid event name');
+        }
+    }
+    public off(eventName: string) {
+        switch(eventName) {
+            case Drag.Event.moveStart:
+                this.moveStart = null;
+                break;
+            case Drag.Event.move:
+                this.move = null;
+                break;
+            case Drag.Event.moveEnd:
+                this.moveEnd = null;
+                break;
+            default:
+                throw new Error('Invalid event name');
+        }
+    }
+
+
+    public destroy() {
+        // 移除事件
+        this.el.removeEventListener('mousedown', this.downEvent);
+        document.removeEventListener('mouseup', this.mouseUp);
+        document.removeEventListener('mousemove', this.mouseMove);
+        this.off(Drag.Event.moveStart);
+        this.off(Drag.Event.move);
+        this.off(Drag.Event.moveEnd);
+    }
+
+}

+ 9 - 9
src/util/pageHandle.ts

@@ -1,19 +1,19 @@
 import {App} from "vue";
 
-import {ipcRenderer} from "electron";
+// import {ipcRenderer} from "electron";
 import {IpcAction, windowAction} from "../tools/IpcCmd.ts";
 import {registerWindowData} from "../types/appConfig.ts";
 
 // 判断是否为 webMode
 
-// const ipcRenderer = {
-//     on: (code: string, _: Function)=>{
-//         console.log(code)
-//     },
-//     send: (code: string, data: any)=>{
-//         console.log(code, data)
-//     },
-// };
+const ipcRenderer = {
+    on: (code: string, _: Function)=>{
+        console.log(code)
+    },
+    send: (code: string, data: any)=>{
+        console.log(code, data)
+    },
+};
 
 function winHandle(ipc: Electron.IpcRenderer, windowName: string, action: IpcAction): boolean{
     let sendCode = action.code;