Jelajahi Sumber

合并冲突

Wind-Breaker1 1 tahun lalu
induk
melakukan
03a3ee5bf7

+ 2 - 2
index.html

@@ -24,10 +24,10 @@
       }
     </style>
     <link
-      href="//at.alicdn.com/t/c/font_4042197_esjv5wscsw6.css"
+      href="//at.alicdn.com/t/c/font_4042197_dopqcs38fz5.css"
       rel="stylesheet"
     />
-    <script src="//at.alicdn.com/t/c/font_4042197_esjv5wscsw6.js"></script>
+    <script src="//at.alicdn.com/t/c/font_4042197_dopqcs38fz5.js"></script>
   </head>
   <body>
     <div id="app"></div>

+ 8 - 8
package.json

@@ -1,9 +1,9 @@
 {
-  "name": "app-studio",
+  "name": "visualization-design",
   "private": true,
   "version": "0.0.1",
   "scripts": {
-    "start": "vite --host --open --port 7000",
+    "start": "vite --host --open --port 80",
     "prod": "vue-tsc --noEmit && vite build --base=https://assets.le5lecdn.com/v/",
     "build": "vue-tsc --noEmit && vite build --mode base --base=/v/",
     "trial": "vue-tsc --noEmit && vite build --mode trial --base=/v/",
@@ -19,16 +19,16 @@
     "localforage": "^1.10.0",
     "monaco-editor": "^0.38.0",
     "qrcode": "^1.5.3",
-    "tdesign-vue-next": "^1.3.7",
-    "vue": "^3.3.2",
+    "tdesign-vue-next": "^1.3.10",
+    "vue": "^3.3.4",
     "vue-router": "^4.2.0"
   },
   "devDependencies": {
     "@types/file-saver": "^2.0.5",
-    "@types/node": "^18.6.4",
+    "@types/node": "^20.4.1",
     "@types/offscreencanvas": "^2019.7.0",
     "@types/qrcode": "^1.5.0",
-    "@vitejs/plugin-vue": "^4.2.0",
+    "@vitejs/plugin-vue": "^4.2.3",
     "@vitejs/plugin-vue-jsx": "^3.0.0",
     "autoprefixer": "^10.4.13",
     "formidable": "^2.0.1",
@@ -36,7 +36,7 @@
     "postcss-import": "^14.1.0",
     "postcss-nested": "^6.0.1",
     "typescript": "^4.7.4",
-    "vite": "^4.3.5",
-    "vue-tsc": "^1.4.4"
+    "vite": "^4.4.2",
+    "vue-tsc": "^1.8.3"
   }
 }

+ 157 - 144
pnpm-lock.yaml

@@ -1,4 +1,4 @@
-lockfileVersion: '6.1'
+lockfileVersion: '6.0'
 
 settings:
   autoInstallPeers: true
@@ -33,10 +33,10 @@ dependencies:
     specifier: ^1.5.3
     version: 1.5.3
   tdesign-vue-next:
-    specifier: ^1.3.7
-    version: 1.3.7(vue@3.3.4)
+    specifier: ^1.3.10
+    version: 1.3.10(vue@3.3.4)
   vue:
-    specifier: ^3.3.2
+    specifier: ^3.3.4
     version: 3.3.4
   vue-router:
     specifier: ^4.2.0
@@ -47,8 +47,8 @@ devDependencies:
     specifier: ^2.0.5
     version: 2.0.5
   '@types/node':
-    specifier: ^18.6.4
-    version: 18.16.12
+    specifier: ^20.4.1
+    version: 20.4.1
   '@types/offscreencanvas':
     specifier: ^2019.7.0
     version: 2019.7.0
@@ -56,11 +56,11 @@ devDependencies:
     specifier: ^1.5.0
     version: 1.5.0
   '@vitejs/plugin-vue':
-    specifier: ^4.2.0
-    version: 4.2.3(vite@4.3.7)(vue@3.3.4)
+    specifier: ^4.2.3
+    version: 4.2.3(vite@4.4.2)(vue@3.3.4)
   '@vitejs/plugin-vue-jsx':
     specifier: ^3.0.0
-    version: 3.0.1(vite@4.3.7)(vue@3.3.4)
+    version: 3.0.1(vite@4.4.2)(vue@3.3.4)
   autoprefixer:
     specifier: ^10.4.13
     version: 10.4.14(postcss@8.4.23)
@@ -78,13 +78,13 @@ devDependencies:
     version: 6.0.1(postcss@8.4.23)
   typescript:
     specifier: ^4.7.4
-    version: 4.9.5
+    version: 4.7.4
   vite:
-    specifier: ^4.3.5
-    version: 4.3.7(@types/node@18.16.12)
+    specifier: ^4.4.2
+    version: 4.4.2(@types/node@20.4.1)
   vue-tsc:
-    specifier: ^1.4.4
-    version: 1.6.5(typescript@4.9.5)
+    specifier: ^1.8.3
+    version: 1.8.3(typescript@4.7.4)
 
 packages:
 
@@ -396,8 +396,8 @@ packages:
       '@babel/helper-validator-identifier': 7.19.1
       to-fast-properties: 2.0.0
 
-  /@esbuild/android-arm64@0.17.19:
-    resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
+  /@esbuild/android-arm64@0.18.11:
+    resolution: {integrity: sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [android]
@@ -405,8 +405,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/android-arm@0.17.19:
-    resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==}
+  /@esbuild/android-arm@0.18.11:
+    resolution: {integrity: sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [android]
@@ -414,8 +414,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/android-x64@0.17.19:
-    resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==}
+  /@esbuild/android-x64@0.18.11:
+    resolution: {integrity: sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [android]
@@ -423,8 +423,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/darwin-arm64@0.17.19:
-    resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==}
+  /@esbuild/darwin-arm64@0.18.11:
+    resolution: {integrity: sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [darwin]
@@ -432,8 +432,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/darwin-x64@0.17.19:
-    resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==}
+  /@esbuild/darwin-x64@0.18.11:
+    resolution: {integrity: sha512-N15Vzy0YNHu6cfyDOjiyfJlRJCB/ngKOAvoBf1qybG3eOq0SL2Lutzz9N7DYUbb7Q23XtHPn6lMDF6uWbGv9Fw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [darwin]
@@ -441,8 +441,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/freebsd-arm64@0.17.19:
-    resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==}
+  /@esbuild/freebsd-arm64@0.18.11:
+    resolution: {integrity: sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [freebsd]
@@ -450,8 +450,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/freebsd-x64@0.17.19:
-    resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==}
+  /@esbuild/freebsd-x64@0.18.11:
+    resolution: {integrity: sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [freebsd]
@@ -459,8 +459,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-arm64@0.17.19:
-    resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==}
+  /@esbuild/linux-arm64@0.18.11:
+    resolution: {integrity: sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [linux]
@@ -468,8 +468,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-arm@0.17.19:
-    resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==}
+  /@esbuild/linux-arm@0.18.11:
+    resolution: {integrity: sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [linux]
@@ -477,8 +477,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-ia32@0.17.19:
-    resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==}
+  /@esbuild/linux-ia32@0.18.11:
+    resolution: {integrity: sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw==}
     engines: {node: '>=12'}
     cpu: [ia32]
     os: [linux]
@@ -486,8 +486,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-loong64@0.17.19:
-    resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==}
+  /@esbuild/linux-loong64@0.18.11:
+    resolution: {integrity: sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw==}
     engines: {node: '>=12'}
     cpu: [loong64]
     os: [linux]
@@ -495,8 +495,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-mips64el@0.17.19:
-    resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==}
+  /@esbuild/linux-mips64el@0.18.11:
+    resolution: {integrity: sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg==}
     engines: {node: '>=12'}
     cpu: [mips64el]
     os: [linux]
@@ -504,8 +504,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-ppc64@0.17.19:
-    resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==}
+  /@esbuild/linux-ppc64@0.18.11:
+    resolution: {integrity: sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ==}
     engines: {node: '>=12'}
     cpu: [ppc64]
     os: [linux]
@@ -513,8 +513,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-riscv64@0.17.19:
-    resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==}
+  /@esbuild/linux-riscv64@0.18.11:
+    resolution: {integrity: sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w==}
     engines: {node: '>=12'}
     cpu: [riscv64]
     os: [linux]
@@ -522,8 +522,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-s390x@0.17.19:
-    resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==}
+  /@esbuild/linux-s390x@0.18.11:
+    resolution: {integrity: sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg==}
     engines: {node: '>=12'}
     cpu: [s390x]
     os: [linux]
@@ -531,8 +531,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-x64@0.17.19:
-    resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==}
+  /@esbuild/linux-x64@0.18.11:
+    resolution: {integrity: sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [linux]
@@ -540,8 +540,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/netbsd-x64@0.17.19:
-    resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==}
+  /@esbuild/netbsd-x64@0.18.11:
+    resolution: {integrity: sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [netbsd]
@@ -549,8 +549,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/openbsd-x64@0.17.19:
-    resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==}
+  /@esbuild/openbsd-x64@0.18.11:
+    resolution: {integrity: sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [openbsd]
@@ -558,8 +558,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/sunos-x64@0.17.19:
-    resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==}
+  /@esbuild/sunos-x64@0.18.11:
+    resolution: {integrity: sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [sunos]
@@ -567,8 +567,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/win32-arm64@0.17.19:
-    resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==}
+  /@esbuild/win32-arm64@0.18.11:
+    resolution: {integrity: sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [win32]
@@ -576,8 +576,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/win32-ia32@0.17.19:
-    resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==}
+  /@esbuild/win32-ia32@0.18.11:
+    resolution: {integrity: sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg==}
     engines: {node: '>=12'}
     cpu: [ia32]
     os: [win32]
@@ -585,8 +585,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/win32-x64@0.17.19:
-    resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==}
+  /@esbuild/win32-x64@0.18.11:
+    resolution: {integrity: sha512-rQI4cjLHd2hGsM1LqgDI7oOCYbQ6IBOVsX9ejuRMSze0GqXUG2ekwiKkiBU1pRGSeCqFFHxTrcEydB2Hyoz9CA==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [win32]
@@ -666,8 +666,8 @@ packages:
     resolution: {integrity: sha512-OuJi8bIng4wYHHA3YpKauL58dZrPxro3d0tabPHyiNF8rKfGKuVfr83oFlPLmKri1cX+Z3cJP39GXmnqkP11Gw==}
     dev: false
 
-  /@types/node@18.16.12:
-    resolution: {integrity: sha512-tIRrjbY9C277MOfP8M3zjMIhtMlUJ6YVqkGgLjz+74jVsdf4/UjC6Hku4+1N0BS0qyC0JAS6tJLUk9H6JUKviQ==}
+  /@types/node@20.4.1:
+    resolution: {integrity: sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==}
     dev: true
 
   /@types/offscreencanvas@2019.7.0:
@@ -677,7 +677,7 @@ packages:
   /@types/qrcode@1.5.0:
     resolution: {integrity: sha512-x5ilHXRxUPIMfjtM+1vf/GPTRWZ81nqscursm5gMznJeK9M0YnZ1c3bEvRLQ0zSSgedLx1J6MGL231ObQGGhaA==}
     dependencies:
-      '@types/node': 18.16.12
+      '@types/node': 20.4.1
     dev: true
 
   /@types/sortablejs@1.15.1:
@@ -692,7 +692,7 @@ packages:
     resolution: {integrity: sha512-aqayTNmeWrZcvnG2MG9eGYI6b7S5fl+yKgPs6bAjOTwPS316R5SxBGKvtSExfyoJU7pIeHJfsHI0Ji41RVMkvQ==}
     dev: false
 
-  /@vitejs/plugin-vue-jsx@3.0.1(vite@4.3.7)(vue@3.3.4):
+  /@vitejs/plugin-vue-jsx@3.0.1(vite@4.4.2)(vue@3.3.4):
     resolution: {integrity: sha512-+Jb7ggL48FSPS1uhPnJbJwWa9Sr90vQ+d0InW+AhBM22n+cfuYqJZDckBc+W3QSHe1WDvewMZfa4wZOtk5pRgw==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
@@ -702,66 +702,39 @@ packages:
       '@babel/core': 7.21.8
       '@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.21.8)
       '@vue/babel-plugin-jsx': 1.1.1(@babel/core@7.21.8)
-      vite: 4.3.7(@types/node@18.16.12)
+      vite: 4.4.2(@types/node@20.4.1)
       vue: 3.3.4
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@vitejs/plugin-vue@4.2.3(vite@4.3.7)(vue@3.3.4):
+  /@vitejs/plugin-vue@4.2.3(vite@4.4.2)(vue@3.3.4):
     resolution: {integrity: sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       vite: ^4.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 4.3.7(@types/node@18.16.12)
+      vite: 4.4.2(@types/node@20.4.1)
       vue: 3.3.4
     dev: true
 
-  /@volar/language-core@1.4.1:
-    resolution: {integrity: sha512-EIY+Swv+TjsWpxOxujjMf1ZXqOjg9MT2VMXZ+1dKva0wD8W0L6EtptFFcCJdBbcKmGMFkr57Qzz9VNMWhs3jXQ==}
+  /@volar/language-core@1.7.10:
+    resolution: {integrity: sha512-18Gmth5M0UI3hDDqhZngjMnb6WCslcfglkOdepRIhGxRYe7xR7DRRzciisYDMZsvOQxDYme+uaohg0dKUxLV2Q==}
     dependencies:
-      '@volar/source-map': 1.4.1
+      '@volar/source-map': 1.7.10
     dev: true
 
-  /@volar/source-map@1.4.1:
-    resolution: {integrity: sha512-bZ46ad72dsbzuOWPUtJjBXkzSQzzSejuR3CT81+GvTEI2E994D8JPXzM3tl98zyCNnjgs4OkRyliImL1dvJ5BA==}
+  /@volar/source-map@1.7.10:
+    resolution: {integrity: sha512-FBpLEOKJpRxeh2nYbw1mTI5sZOPXYU8LlsCz6xuBY3yNtAizDTTIZtBHe1V8BaMpoSMgRysZe4gVxMEi3rDGVA==}
     dependencies:
-      muggle-string: 0.2.2
+      muggle-string: 0.3.1
     dev: true
 
-  /@volar/typescript@1.4.1-patch.2(typescript@4.9.5):
-    resolution: {integrity: sha512-lPFYaGt8OdMEzNGJJChF40uYqMO4Z/7Q9fHPQC/NRVtht43KotSXLrkPandVVMf9aPbiJ059eAT+fwHGX16k4w==}
-    peerDependencies:
-      typescript: '*'
+  /@volar/typescript@1.7.10:
+    resolution: {integrity: sha512-yqIov4wndLU3GE1iE25bU5W6T+P+exPePcE1dFPPBKzQIBki1KvmdQN5jBlJp3Wo+wp7UIxa/RsdNkXT+iFBjg==}
     dependencies:
-      '@volar/language-core': 1.4.1
-      typescript: 4.9.5
-    dev: true
-
-  /@volar/vue-language-core@1.6.5:
-    resolution: {integrity: sha512-IF2b6hW4QAxfsLd5mePmLgtkXzNi+YnH6ltCd80gb7+cbdpFMjM1I+w+nSg2kfBTyfu+W8useCZvW89kPTBpzg==}
-    dependencies:
-      '@volar/language-core': 1.4.1
-      '@volar/source-map': 1.4.1
-      '@vue/compiler-dom': 3.3.4
-      '@vue/compiler-sfc': 3.3.4
-      '@vue/reactivity': 3.3.4
-      '@vue/shared': 3.3.4
-      minimatch: 9.0.0
-      muggle-string: 0.2.2
-      vue-template-compiler: 2.7.14
-    dev: true
-
-  /@volar/vue-typescript@1.6.5(typescript@4.9.5):
-    resolution: {integrity: sha512-er9rVClS4PHztMUmtPMDTl+7c7JyrxweKSAEe/o/Noeq2bQx6v3/jZHVHBe8ZNUti5ubJL/+Tg8L3bzmlalV8A==}
-    peerDependencies:
-      typescript: '*'
-    dependencies:
-      '@volar/typescript': 1.4.1-patch.2(typescript@4.9.5)
-      '@volar/vue-language-core': 1.6.5
-      typescript: 4.9.5
+      '@volar/language-core': 1.7.10
     dev: true
 
   /@vue/babel-helper-vue-transform-on@1.0.2:
@@ -823,6 +796,25 @@ packages:
     resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
     dev: false
 
+  /@vue/language-core@1.8.3(typescript@4.7.4):
+    resolution: {integrity: sha512-AzhvMYoQkK/tg8CpAAttO19kx1zjS3+weYIr2AhlH/M5HebVzfftQoq4jZNFifjq+hyLKi8j9FiDMS8oqA89+A==}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@volar/language-core': 1.7.10
+      '@volar/source-map': 1.7.10
+      '@vue/compiler-dom': 3.3.4
+      '@vue/reactivity': 3.3.4
+      '@vue/shared': 3.3.4
+      minimatch: 9.0.0
+      muggle-string: 0.3.1
+      typescript: 4.7.4
+      vue-template-compiler: 2.7.14
+    dev: true
+
   /@vue/reactivity-transform@3.3.4:
     resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==}
     dependencies:
@@ -862,6 +854,15 @@ packages:
   /@vue/shared@3.3.4:
     resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==}
 
+  /@vue/typescript@1.8.3(typescript@4.7.4):
+    resolution: {integrity: sha512-6bdgSnIFpRYHlt70pHmnmNksPU00bfXgqAISeaNz3W6d2cH0OTfH8h/IhligQ82sJIhsuyfftQJ5518ZuKIhtA==}
+    dependencies:
+      '@volar/typescript': 1.7.10
+      '@vue/language-core': 1.8.3(typescript@4.7.4)
+    transitivePeerDependencies:
+      - typescript
+    dev: true
+
   /ansi-regex@5.0.1:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
@@ -1187,34 +1188,34 @@ packages:
       once: 1.4.0
     dev: false
 
-  /esbuild@0.17.19:
-    resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
+  /esbuild@0.18.11:
+    resolution: {integrity: sha512-i8u6mQF0JKJUlGR3OdFLKldJQMMs8OqM9Cc3UCi9XXziJ9WERM5bfkHaEAy0YAvPRMgqSW55W7xYn84XtEFTtA==}
     engines: {node: '>=12'}
     hasBin: true
     requiresBuild: true
     optionalDependencies:
-      '@esbuild/android-arm': 0.17.19
-      '@esbuild/android-arm64': 0.17.19
-      '@esbuild/android-x64': 0.17.19
-      '@esbuild/darwin-arm64': 0.17.19
-      '@esbuild/darwin-x64': 0.17.19
-      '@esbuild/freebsd-arm64': 0.17.19
-      '@esbuild/freebsd-x64': 0.17.19
-      '@esbuild/linux-arm': 0.17.19
-      '@esbuild/linux-arm64': 0.17.19
-      '@esbuild/linux-ia32': 0.17.19
-      '@esbuild/linux-loong64': 0.17.19
-      '@esbuild/linux-mips64el': 0.17.19
-      '@esbuild/linux-ppc64': 0.17.19
-      '@esbuild/linux-riscv64': 0.17.19
-      '@esbuild/linux-s390x': 0.17.19
-      '@esbuild/linux-x64': 0.17.19
-      '@esbuild/netbsd-x64': 0.17.19
-      '@esbuild/openbsd-x64': 0.17.19
-      '@esbuild/sunos-x64': 0.17.19
-      '@esbuild/win32-arm64': 0.17.19
-      '@esbuild/win32-ia32': 0.17.19
-      '@esbuild/win32-x64': 0.17.19
+      '@esbuild/android-arm': 0.18.11
+      '@esbuild/android-arm64': 0.18.11
+      '@esbuild/android-x64': 0.18.11
+      '@esbuild/darwin-arm64': 0.18.11
+      '@esbuild/darwin-x64': 0.18.11
+      '@esbuild/freebsd-arm64': 0.18.11
+      '@esbuild/freebsd-x64': 0.18.11
+      '@esbuild/linux-arm': 0.18.11
+      '@esbuild/linux-arm64': 0.18.11
+      '@esbuild/linux-ia32': 0.18.11
+      '@esbuild/linux-loong64': 0.18.11
+      '@esbuild/linux-mips64el': 0.18.11
+      '@esbuild/linux-ppc64': 0.18.11
+      '@esbuild/linux-riscv64': 0.18.11
+      '@esbuild/linux-s390x': 0.18.11
+      '@esbuild/linux-x64': 0.18.11
+      '@esbuild/netbsd-x64': 0.18.11
+      '@esbuild/openbsd-x64': 0.18.11
+      '@esbuild/sunos-x64': 0.18.11
+      '@esbuild/win32-arm64': 0.18.11
+      '@esbuild/win32-ia32': 0.18.11
+      '@esbuild/win32-x64': 0.18.11
     dev: true
 
   /escalade@3.1.1:
@@ -1614,8 +1615,8 @@ packages:
     resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
     dev: true
 
-  /muggle-string@0.2.2:
-    resolution: {integrity: sha512-YVE1mIJ4VpUMqZObFndk9CJu6DBJR/GB13p3tXuNbwD4XExaI5EOuRl6BHeIDxIqXZVxSfAC+y6U1Z/IxCfKUg==}
+  /muggle-string@0.3.1:
+    resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
     dev: true
 
   /nanoid@3.3.6:
@@ -1738,6 +1739,15 @@ packages:
       picocolors: 1.0.0
       source-map-js: 1.0.2
 
+  /postcss@8.4.25:
+    resolution: {integrity: sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==}
+    engines: {node: ^10 || ^12 || >=14}
+    dependencies:
+      nanoid: 3.3.6
+      picocolors: 1.0.0
+      source-map-js: 1.0.2
+    dev: true
+
   /process-nextick-args@2.0.1:
     resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
     dev: false
@@ -1829,8 +1839,8 @@ packages:
       glob: 7.2.3
     dev: false
 
-  /rollup@3.22.0:
-    resolution: {integrity: sha512-imsigcWor5Y/dC0rz2q0bBt9PabcL3TORry2hAa6O6BuMvY71bqHyfReAz5qyAqiQATD1m70qdntqBfBQjVWpQ==}
+  /rollup@3.26.2:
+    resolution: {integrity: sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==}
     engines: {node: '>=14.18.0', npm: '>=8.0.0'}
     hasBin: true
     optionalDependencies:
@@ -1947,8 +1957,8 @@ packages:
       vue: 3.3.4
     dev: false
 
-  /tdesign-vue-next@1.3.7(vue@3.3.4):
-    resolution: {integrity: sha512-AMo4TQmlfqDHq/pvvMvzthEG37yCZrbdm/2XJanYFs+2g1foO2a1NyKSFccm0SdslaWFJj3hQFYsBBr/IlMUUg==}
+  /tdesign-vue-next@1.3.10(vue@3.3.4):
+    resolution: {integrity: sha512-ab5aO56gFmzgyIwUkeR9XG2q1YEnRxI4yWzYPl12y81gfS32Nb2fUnHjHjnkBA6d0o/5tMhUfCBQoJIE+5GRjg==}
     peerDependencies:
       vue: '>=3.1.0'
     dependencies:
@@ -1987,8 +1997,8 @@ packages:
     resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==}
     dev: false
 
-  /typescript@4.9.5:
-    resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
+  /typescript@4.7.4:
+    resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
     engines: {node: '>=4.2.0'}
     hasBin: true
     dev: true
@@ -2032,13 +2042,14 @@ packages:
     engines: {node: '>= 0.10'}
     dev: false
 
-  /vite@4.3.7(@types/node@18.16.12):
-    resolution: {integrity: sha512-MTIFpbIm9v7Hh5b0wSBgkcWzSBz7SAa6K/cBTwS4kUiQJfQLFlZZRJRQgqunCVzhTPCk674tW+0Qaqh3Q00dBg==}
+  /vite@4.4.2(@types/node@20.4.1):
+    resolution: {integrity: sha512-zUcsJN+UvdSyHhYa277UHhiJ3iq4hUBwHavOpsNUGsTgjBeoBlK8eDt+iT09pBq0h9/knhG/SPrZiM7cGmg7NA==}
     engines: {node: ^14.18.0 || >=16.0.0}
     hasBin: true
     peerDependencies:
       '@types/node': '>= 14'
       less: '*'
+      lightningcss: ^1.21.0
       sass: '*'
       stylus: '*'
       sugarss: '*'
@@ -2048,6 +2059,8 @@ packages:
         optional: true
       less:
         optional: true
+      lightningcss:
+        optional: true
       sass:
         optional: true
       stylus:
@@ -2057,10 +2070,10 @@ packages:
       terser:
         optional: true
     dependencies:
-      '@types/node': 18.16.12
-      esbuild: 0.17.19
-      postcss: 8.4.23
-      rollup: 3.22.0
+      '@types/node': 20.4.1
+      esbuild: 0.18.11
+      postcss: 8.4.25
+      rollup: 3.26.2
     optionalDependencies:
       fsevents: 2.3.2
     dev: true
@@ -2081,16 +2094,16 @@ packages:
       he: 1.2.0
     dev: true
 
-  /vue-tsc@1.6.5(typescript@4.9.5):
-    resolution: {integrity: sha512-Wtw3J7CC+JM2OR56huRd5iKlvFWpvDiU+fO1+rqyu4V2nMTotShz4zbOZpW5g9fUOcjnyZYfBo5q5q+D/q27JA==}
+  /vue-tsc@1.8.3(typescript@4.7.4):
+    resolution: {integrity: sha512-Ua4DHuYxjudlhCW2nRZtaXbhIDVncRGIbDjZhHpF8Z8vklct/G/35/kAPuGNSOmq0JcvhPAe28Oa7LWaUerZVA==}
     hasBin: true
     peerDependencies:
       typescript: '*'
     dependencies:
-      '@volar/vue-language-core': 1.6.5
-      '@volar/vue-typescript': 1.6.5(typescript@4.9.5)
+      '@vue/language-core': 1.8.3(typescript@4.7.4)
+      '@vue/typescript': 1.8.3(typescript@4.7.4)
       semver: 7.5.1
-      typescript: 4.9.5
+      typescript: 4.7.4
     dev: true
 
   /vue@3.3.4:

TEMPAT SAMPAH
public/data.xlsx


+ 18 - 14
src/services/common.ts

@@ -211,7 +211,7 @@ export const save = async (
       query: {
         id: data._id,
         r: Date.now() + '',
-        component: data.component + '',
+        c: data.component ? 1 : undefined,
       },
     });
   }
@@ -329,7 +329,7 @@ export const onScaleWindow = () => {
   meta2d.fitSizeView(true, 32);
 };
 
-export const onScaleView = () => {
+export const onScaleFull = () => {
   meta2d.scale(1);
   // meta2d.centerView();
   const { x, y, origin, center } = meta2d.store.data;
@@ -372,18 +372,18 @@ const tree = reactive({
 });
 
 export const getPenTree = () => {
-  if (tree.patch) {
-    tree.patch = false;
-    const list = [];
-    for (const item of meta2d.store.data.pens) {
-      if (item.parentId) {
-        continue;
-      }
-      const elem = calcElem(item);
-      elem && list.push(elem);
+  // if (tree.patch) {
+  tree.patch = false;
+  const list = [];
+  for (const item of meta2d.store.data.pens) {
+    if (item.parentId) {
+      continue;
     }
-    tree.list = list;
+    const elem = calcElem(item);
+    elem && list.push(elem);
   }
+  tree.list = list;
+  // }
 
   return tree.list;
 };
@@ -487,8 +487,12 @@ export const typeOptions = [
     value: 'string',
   },
   {
-    label: '数字',
-    value: 'number',
+    label: '整数',
+    value: 'integer',
+  },
+  {
+    label: '浮点数',
+    value: 'float',
   },
   {
     label: '布尔',

+ 4 - 4
src/services/debouce.ts

@@ -1,7 +1,7 @@
 const debounces = new WeakMap();
 const throttles = new WeakMap();
 
-export function debounce(fn: Function, delay: number) {
+export function debounce(fn: Function, delay: number, params?: any) {
   let cache: any = debounces.get(fn);
   if (cache) {
     clearTimeout(cache.timer);
@@ -11,13 +11,13 @@ export function debounce(fn: Function, delay: number) {
   }
   return new Promise((resolve, reject) => {
     cache.timer = setTimeout(async () => {
-      resolve(await fn());
+      resolve(await fn(params));
       debounces.delete(fn);
     }, delay);
   });
 }
 
-export async function throttle(fn: Function, delay: number) {
+export async function throttle(fn: Function, delay: number, params?: any) {
   const now = new Date().getTime();
   const start: number = debounces.get(fn);
   throttles.set(fn, now);
@@ -25,5 +25,5 @@ export async function throttle(fn: Function, delay: number) {
     return;
   }
 
-  return await fn();
+  return await fn(params);
 }

+ 503 - 49
src/services/defaults.ts

@@ -931,7 +931,7 @@ export const formComponents = [
           width: 160,
           height: 30,
           name: 'text',
-          text: 'le5le-meta2d',
+          text: '乐吾乐le5le - 大屏可视化',
         },
       },
       {
@@ -959,6 +959,22 @@ export const formComponents = [
           progress: 0.8,
           background: 'rgba(225, 227, 232, 1)',
           lineWidth: 0,
+          props: {
+            custom: [
+              {
+                key: 'progress',
+                label: '进度',
+                type: 'number',
+                max: 1,
+                min: 0,
+              },
+              {
+                key: 'progressColor',
+                label: '进度颜色',
+                type: 'color',
+              },
+            ],
+          },
         },
       },
       {
@@ -968,7 +984,7 @@ export const formComponents = [
           width: 100,
           height: 100,
           name: 'image',
-          icon: '\uea35',
+          icon: '\uea86',
           iconFamily: 'l-icon',
         },
       },
@@ -979,7 +995,7 @@ export const formComponents = [
           width: 100,
           height: 100,
           name: 'image',
-          image: cdn + '/img/logo.png',
+          image: (cdn ? cdn + '/v' : '') + '/img/logo.png',
         },
       },
       {
@@ -989,7 +1005,7 @@ export const formComponents = [
           width: 100,
           height: 100,
           name: 'gif',
-          image: cdn + '/png/电信机房/防火墙.gif',
+          image: (cdn ? cdn + '/v' : '') + '/png/电信机房/防火墙.gif',
         },
       },
       {
@@ -1002,6 +1018,15 @@ export const formComponents = [
           name: 'video',
           video:
             'https://video.699pic.com/videos/17/69/11/a_aa3jeKZ0D63g1556176911_10s.mp4',
+          props: {
+            custom: [
+              {
+                key: 'video',
+                label: '视频地址',
+                type: 'string',
+              },
+            ],
+          },
         },
       },
       {
@@ -1016,6 +1041,29 @@ export const formComponents = [
             'https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv',
           mediaDataSource: {},
           optionalConfig: {},
+          props: {
+            custom: [
+              {
+                key: 'video',
+                label: '视频地址',
+                type: 'string',
+              },
+              {
+                key: 'mediaDataSource',
+                label: '媒体数据源',
+                type: 'code',
+                placeholder:
+                  '具体配置参考:https://github.com/bilibili/flv.js/blob/master/docs/api.md',
+              },
+              {
+                key: 'optionalConfig',
+                label: '配置',
+                type: 'code',
+                placeholder:
+                  '具体配置参考:https://github.com/bilibili/flv.js/blob/master/docs/api.md',
+              },
+            ],
+          },
         },
       },
       {
@@ -1023,10 +1071,19 @@ export const formComponents = [
         icon: 'l-yinpin',
         data: {
           width: 200,
-          height: 200,
+          height: 40,
           externElement: true,
           name: 'video',
           audio: 'https://www.xzmp3.com/down/597caee79849.mp3',
+          props: {
+            custom: [
+              {
+                key: 'audio',
+                label: '视频地址',
+                type: 'string',
+              },
+            ],
+          },
         },
       },
       {
@@ -1038,7 +1095,7 @@ export const formComponents = [
           disableAnchor: true,
           name: 'square',
           lineWidth: 0,
-          image: '/img/avatar.png',
+          image: (cdn ? cdn + '/v' : '') + '/img/avatar.png',
           imageRadius: 0.5,
           background: '#689f38',
           borderRadius: 0.5,
@@ -1098,6 +1155,15 @@ export const formComponents = [
           text: '当前时间',
           timeFormat:
             '`${year}年${month}月${day}日 ${hours}时${minutes}分${seconds}秒 星期${week}`',
+          props: {
+            custom: [
+              {
+                key: 'timeFormat',
+                label: '显示格式',
+                type: 'string',
+              },
+            ],
+          },
         },
       },
       {
@@ -1111,6 +1177,20 @@ export const formComponents = [
           calculativeTime: '2023/9/23 00:00:00', //配置未来的时间
           timeFormat:
             '`距离杭州亚运会还有:${day}天${hours}时${minutes}分${seconds}秒`',
+          props: {
+            custom: [
+              {
+                key: 'timeFormat',
+                label: '显示格式',
+                type: 'string',
+              },
+              {
+                key: 'calculativeTime',
+                label: '参考时间',
+                type: 'string',
+              },
+            ],
+          },
         },
       },
       {
@@ -1148,6 +1228,33 @@ export const formComponents = [
             },
           ],
           text: '时间轴',
+          props: {
+            custom: [
+              {
+                key: 'mode',
+                label: '标签分布',
+                type: 'select',
+                options: [
+                  { label: '同侧', value: 'same' },
+                  { label: '交叉', value: 'alternate' },
+                ],
+              },
+              {
+                key: 'labelAlign',
+                label: '标签对齐',
+                type: 'select',
+                options: [
+                  { label: '顶部', value: 'top' },
+                  { label: '底部', value: 'bottom' },
+                ],
+              },
+              {
+                key: 'data',
+                label: '数据',
+                type: 'code',
+              },
+            ],
+          },
         },
       },
       {
@@ -1185,6 +1292,33 @@ export const formComponents = [
               content: '事件四',
             },
           ],
+          props: {
+            custom: [
+              {
+                key: 'mode',
+                label: '标签分布',
+                type: 'select',
+                options: [
+                  { label: '同侧', value: 'same' },
+                  { label: '交叉', value: 'alternate' },
+                ],
+              },
+              {
+                key: 'labelAlign',
+                label: '标签对齐',
+                type: 'select',
+                options: [
+                  { label: '靠左', value: 'left' },
+                  { label: '靠右', value: 'right' },
+                ],
+              },
+              {
+                key: 'data',
+                label: '数据',
+                type: 'code',
+              },
+            ],
+          },
         },
       },
       {
@@ -1193,6 +1327,7 @@ export const formComponents = [
         data: {
           width: 200,
           height: 200,
+          hiddenText: true,
           name: 'calendar',
         },
       },
@@ -1204,10 +1339,37 @@ export const formComponents = [
     list: [
       {
         name: '列表',
-        icon: 'l-pc', //l-liebiao
+        icon: 'l-liebiao',
         data: {
-          width: 200,
+          width: 400,
           height: 200,
+          name: 'list',
+          headingColor: '#000',
+          headingSize: 16,
+          background: '#fff',
+          data: [
+            {
+              title: '列表标题',
+              description: '列表内容的描述性文字',
+            },
+            {
+              title: '列表标题',
+              description: '列表内容的描述性文字',
+            },
+            {
+              title: '列表标题',
+              description: '列表内容的描述性文字',
+            },
+          ],
+          props: {
+            custom: [
+              {
+                key: 'data',
+                label: '数据',
+                type: 'code',
+              },
+            ],
+          },
         },
       },
       {
@@ -1300,6 +1462,145 @@ export const formComponents = [
               ],
             },
           ],
+          props: {
+            custom: [
+              {
+                key: 'data',
+                label: '数据',
+                type: 'code',
+              },
+              {
+                key: 'styles',
+                label: '样式',
+                type: 'code',
+              },
+            ],
+          },
+        },
+      },
+      {
+        name: '斑马纹表格',
+        icon: 'l-biaoge',
+        data: {
+          name: 'table2',
+          width: 0,
+          height: 0,
+          disableAnchor: true,
+          colWidth: 90,
+          rowHeight: 32,
+          bordered: false,
+          vLine: false,
+          hLine: false,
+          stripe: true,
+          stripeColor: '#407FFF1F', //'#15181c',
+          // hasHeader: false,
+          textColor: '#FFFFFF',
+          data: [
+            ['设备 ID', '设备名称', '数据协议', '级别'],
+            [
+              '1',
+              '200',
+              'MQTT',
+              {
+                text: '一级告警',
+                background: '#650b09',
+                textColor: '#FF5D3CFF',
+              },
+            ],
+            [
+              '2',
+              '湿度传感器',
+              'MQTT',
+              {
+                text: '二级告警',
+                background: '#4d2a02',
+                textColor: '#E6A82EFF',
+              },
+            ],
+            [
+              '3',
+              '物联网设备',
+              'MQTT',
+              {
+                text: '一级告警',
+                background: '#650b09',
+                textColor: '#FF5D3CFF',
+              },
+            ],
+            [
+              '4',
+              '物联网设备/智能家居/智慧城市',
+              'MQTT',
+              {
+                text: '三级告警',
+                textColor: '#58CC84FF',
+                background: '#042617;',
+              },
+            ],
+          ],
+          styles: [
+            {
+              row: 0,
+              textColor: '#ffffff66',
+            },
+            {
+              col: 3,
+              pens: [
+                {
+                  name: 'rectangle',
+                  width: 56,
+                  height: 20,
+                  fontSize: 0.6,
+                  disableAnchor: true,
+                  lineWidth: 0,
+                },
+              ],
+            },
+          ],
+          props: {
+            custom: [
+              {
+                key: 'stripe',
+                label: '显示斑马纹',
+                type: 'bool',
+              },
+              {
+                key: 'stripeColor',
+                label: '斑马纹颜色',
+                type: 'color',
+              },
+              {
+                key: 'bordered',
+                label: '外边框',
+                type: 'bool',
+              },
+              {
+                key: 'hLine',
+                label: '水平线',
+                type: 'bool',
+              },
+              {
+                key: 'vLine',
+                label: '垂直线',
+                type: 'bool',
+              },
+              {
+                key: 'hasHeader',
+                label: '有无头部',
+                type: 'bool',
+              },
+              {
+                key: 'data',
+                label: '数据',
+                type: 'code',
+              },
+              {
+                key: 'styles',
+                label: '样式',
+                type: 'code',
+              },
+            ],
+          },
         },
       },
       {
@@ -1368,13 +1669,22 @@ export const formComponents = [
       },
       {
         name: '网页',
-        icon: 'l-pc',
+        icon: 'l-iframe',
         data: {
           name: 'iframe',
           width: 500,
           height: 400,
           externElement: true,
           iframe: 'https://le5le.com',
+          props: {
+            custom: [
+              {
+                key: 'iframe',
+                label: '网页地址',
+                type: 'string',
+              },
+            ],
+          },
         },
       },
     ],
@@ -1413,6 +1723,15 @@ export const formComponents = [
               text: '20%',
             },
           ],
+          props: {
+            custom: [
+              {
+                key: 'data',
+                label: '数据',
+                type: 'code',
+              },
+            ],
+          },
         },
       },
       {
@@ -1436,6 +1755,7 @@ export const formComponents = [
           textLeft: 30,
           shadow: false,
           textColor: 'rgba(0, 0, 0, 1)',
+          hoverTextColor: 'rgba(0, 0, 0, 1)',
           shadowColor: 'rgba(250, 247, 247, 0.5)',
           shadowOffsetX: 6,
           shadowOffsetY: 6,
@@ -1446,8 +1766,63 @@ export const formComponents = [
         name: '通知',
         icon: 'l-tongzhi',
         data: {
-          width: 200,
-          height: 200,
+          width: 360,
+          height: 178,
+          text: '通知具体内容;通知具体内容;通知具体内容;通知具体内容;通知具体内容;通知具体内容;通知具体内容;通知具体内容;通知具体内容;',
+          borderRadius: 6,
+          name: 'notification',
+          icon: '\ue6e4',
+          iconFamily: 'l-icon',
+          iconColor: '#4583ff',
+          iconSize: 17.5,
+          iconAlign: 'left-top',
+          iconLeft: 8,
+          iconTop: 8,
+          textAlign: 'left',
+          textBaseline: 'top',
+          textLeft: 30,
+          textTop: 30,
+          color: 'rgba(235, 235, 235, 1)',
+          background: 'rgba(255, 255, 255, 1)',
+          shadow: false,
+          textColor: '#666666',
+          hoverTextColor: '#666666',
+          shadowColor: 'rgba(250, 247, 247, 0.5)',
+          shadowOffsetX: 6,
+          shadowOffsetY: 6,
+          shadowBlur: 20,
+          heading: '标题名称',
+          headingColor: '#000',
+          headingSize: 16,
+          // headingWeight: 'bold',
+          props: {
+            custom: [
+              {
+                key: 'heading',
+                label: '标题',
+                type: 'string',
+              },
+              {
+                key: 'headingColor',
+                label: '标题颜色',
+                type: 'color',
+              },
+              {
+                key: 'headingSize',
+                label: '标题大小',
+                type: 'number',
+              },
+              {
+                key: 'headingWeight',
+                label: '标题加粗',
+                type: 'select',
+                options: [
+                  { label: '加粗', value: 'bold' },
+                  { label: '正常', value: '' },
+                ],
+              },
+            ],
+          },
         },
       },
       // {
@@ -1466,18 +1841,32 @@ export const formComponents = [
     list: [
       {
         name: '水平轮播',
-        icon: 'l-pc',
+        icon: 'l-paomadeng',
         data: {
           name: 'leSwiperline',
           width: 200,
           height: 40,
           data: ['轮播第一次数据', '轮播第二次数据', '轮播第三次数据'],
           hiddenText: true,
+          props: {
+            custom: [
+              {
+                key: 'data',
+                label: '数据',
+                type: 'code',
+              },
+              {
+                key: 'timeout',
+                label: '轮播时间',
+                type: 'number',
+              },
+            ],
+          },
         },
       },
       {
         name: '垂直轮播',
-        icon: 'l-pc',
+        icon: 'l-chuizhigundong',
         data: {
           name: 'leSwiperline',
           width: 200,
@@ -1486,6 +1875,20 @@ export const formComponents = [
           lineHeight: 2,
           data: ['轮播第一次数据', '轮播第二次数据', '轮播第三次数据'],
           hiddenText: true,
+          props: {
+            custom: [
+              {
+                key: 'data',
+                label: '数据',
+                type: 'code',
+              },
+              {
+                key: 'timeout',
+                label: '轮播时间',
+                type: 'number',
+              },
+            ],
+          },
         },
       },
       {
@@ -1495,18 +1898,6 @@ export const formComponents = [
           name: 'leSwiper',
           width: 400,
           height: 300,
-          // swiperType: 'iframe',
-          // data: [
-          //   {
-          //     src: 'https://2d.le5le.com/preview?id=6357a9e2d44b9402de84d2e8',
-          //   },
-          //   {
-          //     src: 'https://2d.le5le.com/preview?id=6357aec8d44b9402de84d2f1',
-          //   },
-          //   {
-          //     src: 'https://2d.le5le.com/preview?id=641d524a8df2c654ea652d7e',
-          //   },
-          // ],
           data: [
             {
               src: 'https://2ds.le5le.com/img/banner1.bc890350.png',
@@ -1519,11 +1910,20 @@ export const formComponents = [
             },
           ],
           hiddenText: true,
+          props: {
+            custom: [
+              {
+                key: 'data',
+                label: '数据',
+                type: 'code',
+              },
+            ],
+          },
         },
       },
       {
         name: '轮播页面',
-        icon: 'l-pc',
+        icon: 'l-lunboyemian',
         data: {
           name: 'leSwiper',
           width: 400,
@@ -1541,6 +1941,15 @@ export const formComponents = [
             },
           ],
           hiddenText: true,
+          props: {
+            custom: [
+              {
+                key: 'data',
+                label: '数据',
+                type: 'code',
+              },
+            ],
+          },
         },
       },
     ],
@@ -1593,18 +2002,63 @@ export const formComponents = [
       },
       {
         name: '锚点',
-        icon: 'l-pc', //l-anchor
+        icon: 'l-anchor',
         data: {
-          width: 200,
-          height: 200,
+          width: 40,
+          height: 40,
+          name: 'image',
+          icon: '\uea7b',
+          iconFamily: 'l-icon',
+          events: [
+            {
+              action: 5,
+              name: 'click',
+              params: 'pen.id/tag',
+              value:
+                'let pens = context.meta2d.find(params);\r\nif (!pens.length) {\r\n    pens = [pen]\r\n}\r\ncontext.meta2d.active(pens, true);\r\ncontext.meta2d.gotoView(pens[0]);\r\ncontext.meta2d.resize();\r\ncontext.meta2d.render();',
+              where: { type: null },
+            },
+          ],
         },
       },
       {
         name: '回到顶部',
-        icon: 'l-pc', //l-huidaodingbu
+        icon: 'l-huidaodingbu',
         data: {
-          width: 200,
-          height: 200,
+          width: 64,
+          height: 64,
+          name: 'rectangle',
+          activeBackground: '#eee',
+          background: 'rgba(255, 255, 255, 1)',
+          borderRadius: 0.1,
+          color: '#eee',
+          hoverBackground: '#eee',
+          hoverColor: '#eee',
+          hoverTextColor: '#c5c5c5',
+          iconAlign: 'top',
+          iconColor: 'rgba(0, 0, 0, 1)',
+          iconSize: 20,
+          iconTop: 10,
+          text: 'TOP',
+          textBaseline: 'bottom',
+          textColor: '#c5c5c5',
+          textTop: -2,
+          icon: '\ue6a6',
+          iconFamily: 'l-icon',
+          events: [
+            {
+              action: 5,
+              name: 'click',
+              // params: 'pen.id/tag',
+              value: `
+              const { scale, origin, y: dataY } = context.meta2d.store.data;
+              context.meta2d.translate(
+                0,
+                ((parseInt(params)||32) * context.meta2d.store.data.scale - origin.y) / scale - dataY / scale
+              );`,
+              where: { type: null },
+            },
+          ],
         },
       },
       {
@@ -1771,7 +2225,7 @@ export const formComponents = [
       },
       {
         name: '步骤条',
-        icon: 'l-pc', //l-buzhoutiao
+        icon: 'l-buzhoutiao',
         data: {
           name: 'steps',
           width: 500,
@@ -1780,30 +2234,30 @@ export const formComponents = [
           direction: 'horizontal',
           labelAlign: '', //left/right/alternate/top/bottom
           mode: 'alternate', //alternate
+          current: 2,
           data: [
             {
-              label: '2022-01-01',
-              content: '事件一',
+              label: '已完成的步骤',
+              content: '内容',
               // path: 'M71.3,61.9v2.21L56.66,66V63.82l3.27-.43.25-1-2.39.31V60.84l2.84-.37.21-.87-3.4.44V57.85l13.08-1.73v2.2l-6.85.9-.21.88,6-.79-.15,2.85ZM58.11,66.56,69.86,65v5.63L58.11,72.19Zm2.59,3.09,6.44-.84V67.52l-6.44.85ZM62.78,63l3.87-.51.07-.95L63,62.05Z',
             },
             {
-              label: '2022-01-01',
-              content: '事件一',
+              label: '已完成的步骤',
+              content: '内容',
             },
             {
-              label: '2022-02-01',
-              content: '事件二',
+              label: '进行中的步骤',
+              content: '内容',
             },
             {
-              label: '2022-03-01',
-              content: '事件三',
+              label: '未进行中的步骤',
+              content: '内容',
             },
             {
-              label: '2022-04-01',
-              content: '事件四',
+              label: '未进行中的步骤',
+              content: '内容',
             },
           ],
-          text: '时间轴',
         },
       },
       {
@@ -1821,13 +2275,13 @@ export const formComponents = [
             { text: '场景一', key: '0', isForbidden: true },
             { text: '场景二', key: '1' },
             { text: '场景三', key: '2' },
-            { text: '场景四', key: '3' }
-          ]
+            { text: '场景四', key: '3' },
+          ],
         },
       },
       {
         name: '多选选项卡',
-        icon: 'l-pc',
+        icon: 'l-duoxiangxuanxiangka',
         data: {
           name: 'leTab',
           width: 500,
@@ -1839,8 +2293,8 @@ export const formComponents = [
             { text: '场景一', key: '0' },
             { text: '场景二', key: '1', isForbidden: true },
             { text: '场景三', key: '2' },
-            { text: '场景四', key: '3' }
-          ]
+            { text: '场景四', key: '3' },
+          ],
         },
       },
     ],

+ 67 - 3
src/services/echarts.ts

@@ -1,8 +1,9 @@
 import { ReplaceMode } from '@meta2d/chart-diagram';
+import { cdn } from './api';
 
 //注册所有主题
 export function registerTheme() {
-  fetch('theme/dark.json')
+  fetch((cdn ? cdn + '/v/' : '') + 'theme/dark.json')
     .then((r) => r.json())
     .then((theme) => {
       echarts.registerTheme('le-dark', theme);
@@ -62,8 +63,13 @@ export const charts = [
           },
           realTimes: [
             {
-              key: 'echarts.option.series.0.data',
-              label: '数据',
+              key: 'echarts.option.series.0.data.0',
+              label: '一月数据',
+              type: 'object',
+            },
+            {
+              key: 'echarts.option.series.0.data.1',
+              label: '二月数据',
               type: 'object',
             },
             {
@@ -143,6 +149,64 @@ export const charts = [
           ],
         },
       },
+      {
+        name: '时间动态数据折线图',
+        icon: 'l-line-chart',
+        data: {
+          name: 'echarts',
+          width: 366,
+          height: 206,
+          externElement: true,
+          disableAnchor: true,
+          echarts: {
+            option: {
+              grid: {
+                top: 20,
+                bottom: 40,
+                left: 40,
+                right: 20,
+              },
+              xAxis: {
+                type: 'category',
+                data: ['1月', '2月', '3月', '4月', '5月', '6月'],
+              },
+              yAxis: {
+                type: 'value',
+              },
+              series: [
+                {
+                  type: 'line',
+                  data: [40, 20, 90, 60, 70, 80],
+                },
+              ],
+            },
+            replaceMode: 0,
+            max: 100,
+          },
+          realTimes: [
+            {
+              key: 'echarts.option',
+              label: 'echarts',
+              type: 'object',
+            },
+            {
+              key: 'echarts.option.series.0.data',
+              label: '实时数据',
+              type: 'object',
+            },
+            {
+              key: 'echarts.max',
+              label: '最大数量',
+              type: 'number',
+            },
+            {
+              key: 'echarts.replaceMode',
+              label: '模式',
+              type: 'number',
+            },
+          ],
+        },
+      },
     ],
   },
   {

+ 50 - 14
src/styles/app.css

@@ -100,8 +100,27 @@ h5 {
   cursor: pointer !important;
 }
 
-.hover-background:hover {
-  background-color: var(--color-background-popup-hover);
+.hover-background {
+  &:hover {
+    background-color: var(--color-background-popup-hover);
+  }
+
+  &.item {
+    position: relative;
+    line-height: 1.5;
+    padding: 8px 20px 8px 8px;
+    border-radius: 2px;
+    & > .del {
+      font-size: 14px;
+      position: absolute;
+      right: 4px;
+      top: calc(50% - 12px);
+
+      &:hover {
+        color: var(--color-error);
+      }
+    }
+  }
 }
 
 .ellipsis {
@@ -127,6 +146,10 @@ a.hover:hover {
   color: var(--color-primary-hover) !important;
 }
 
+.inline {
+  display: inline-block;
+}
+
 .button {
   display: inline-block !important;
   padding: 0 16px;
@@ -255,6 +278,10 @@ a.hover:hover {
   width: 100%;
 }
 
+.mt-4 {
+  margin-top: 4px;
+}
+
 .ml-4 {
   margin-left: 4px;
 }
@@ -263,6 +290,10 @@ a.hover:hover {
   margin-right: 4px;
 }
 
+.mt-8 {
+  margin-top: 8px;
+}
+
 .mr-8 {
   margin-right: 8px;
 }
@@ -283,18 +314,6 @@ a.hover:hover {
   margin-left: 12px;
 }
 
-.ml-16 {
-  margin-left: 16px;
-}
-
-.mt-4 {
-  margin-top: 4px;
-}
-
-.mt-8 {
-  margin-top: 8px;
-}
-
 .mt-12 {
   margin-top: 12px;
 }
@@ -307,10 +326,19 @@ a.hover:hover {
   margin-right: 12px;
 }
 
+.mx-12 {
+  margin-left: 12px;
+  margin-right: 12px;
+}
+
 .mt-16 {
   margin-top: 16px;
 }
 
+.ml-16 {
+  margin-left: 16px;
+}
+
 .mr-16 {
   margin-right: 16px;
 }
@@ -327,6 +355,10 @@ a.hover:hover {
   margin-top: 24px;
 }
 
+.ml-24 {
+  margin-left: 24px;
+}
+
 .px-4 {
   padding-left: 4px;
   padding-right: 4px;
@@ -388,6 +420,10 @@ a.hover:hover {
   padding: 16px;
 }
 
+.nowrap {
+  white-space: nowrap;
+}
+
 .border {
   border: 1px solid var(--color-sub-border);
 }

+ 18 - 0
src/styles/tdesign.css

@@ -64,6 +64,20 @@
   &.t-is-selected {
     background: none;
     color: var(--color);
+    &.t-is-disabled:hover {
+      background: none !important ;
+    }
+  }
+}
+
+.select-options {
+  .t-select-option {
+    height: 100%;
+    padding: 0;
+
+    &:not(.t-is-disabled):not(.t-is-selected):hover {
+      background: none !important ;
+    }
   }
 }
 
@@ -208,6 +222,10 @@
 .t-table {
   font-size: 13px;
 
+  tr > th {
+    background-color: var(--td-bg-color-secondarycontainer);
+  }
+
   .t-table__pagination {
     padding: 16px 0;
 

+ 3 - 3
src/views/Index.vue

@@ -1,11 +1,11 @@
 <template>
-  <div class="page-app" @contextmenu.prevent>
+  <div class="app-page" @contextmenu.prevent>
     <Header />
 
     <div class="design-body">
       <Graphics />
       <View />
-      <div style="border-left: 1px solid var(--color-border); z-index: 7">
+      <div style="border-left: 1px solid var(--color-border); z-index: 2">
         <FileProps v-if="selections.mode === SelectionMode.File" />
         <PenProps v-else-if="selections.mode === SelectionMode.Pen" />
         <PensProps v-else />
@@ -28,7 +28,7 @@ const { selections } = useSelection();
 </script>
 
 <style lang="postcss" scoped>
-.page-app {
+.app-page {
   height: 100vh;
 
   .design-body {

+ 2 - 2
src/views/components/Actions.vue

@@ -447,7 +447,7 @@ const onChangeAction = (action: any) => {
       action.targetType = 'id';
       break;
     case 15:
-      action.network = { options: {} };
+      action.network = { type: 'publish', protocol: 'mqtt', options: {} };
       action.params = '';
       action.value = {};
       action.targetType = 'id';
@@ -506,7 +506,7 @@ const getProps = (c: any) => {
   } else if (meta2d.store.active) {
     target = meta2d.store.active[0];
   }
-  if (target) {
+  if (target?.realTimes) {
     for (const item of target.realTimes) {
       const found = c.props.findIndex((elem: any) => elem.value === item.key);
       if (found < 0) {

+ 204 - 0
src/views/components/Dataset.vue

@@ -0,0 +1,204 @@
+<template>
+  <div class="dataset-component">
+    <div class="form-item mt-8">
+      <label>数据集名称</label>
+      <t-input v-model="modelValue.name" placeholder="名称" />
+    </div>
+
+    <div class="form-item mt-8">
+      <label>数据方式</label>
+      <t-radio-group v-model="modelValue.mode">
+        <t-radio value="api">HTTP请求</t-radio>
+        <t-radio value="">自定义</t-radio>
+      </t-radio-group>
+    </div>
+    <div v-if="modelValue.mode === 'api'" class="form-item mt-8">
+      <label>URL地址</label>
+      <t-input v-model="modelValue.url" @blur="getDatas" @enter="getDatas" />
+    </div>
+    <template v-else>
+      <div class="form-item mt-8">
+        <label>从Excel导入</label>
+        <div class="flex middle w-full">
+          <t-button
+            class="shrink-0"
+            style="width: 90px; height: 30px"
+            @click="importDataset"
+          >
+            导入Excel
+          </t-button>
+          <a href="/data.xlsx" download class="ml-12 mt-4 nowrap">
+            下载Excel示例
+          </a>
+          <span class="flex-grow"></span>
+          <a class="ml-12 mt-4 nowrap" @click="showAddData()"> + 添加数据 </a>
+        </div>
+      </div>
+    </template>
+
+    <t-table
+      class="mt-16"
+      row-key="id"
+      :data="modelValue.data"
+      :columns="datasetColumns"
+      size="small"
+    >
+      <template #type="{ row }">
+        {{ row.type || 'string' }}
+      </template>
+      <template v-if="!modelValue.mode" #actions="{ row, rowIndex }">
+        <t-icon name="edit" class="hover" @click="showAddData(row, rowIndex)" />
+        <t-icon
+          name="delete"
+          class="ml-12 hover"
+          @click="modelValue.data.splice(rowIndex, 1)"
+        />
+      </template>
+    </t-table>
+
+    <t-dialog
+      v-if="addDataDialog.show"
+      :visible="true"
+      class="data-dialog"
+      :header="addDataDialog.header"
+      @close="addDataDialog.show = false"
+      @confirm="onOkAddData"
+    >
+      <div class="form-item mt-16">
+        <label>数据点名称</label>
+        <t-input v-model="addDataDialog.data.label" placeholder="简短描述" />
+      </div>
+      <div class="form-item mt-16">
+        <label>数据点ID</label>
+        <t-input v-model="addDataDialog.data.id" placeholder="数据点ID" />
+      </div>
+      <div class="form-item mt-16">
+        <label>类型</label>
+        <t-select
+          class="w-full"
+          :options="typeOptions"
+          v-model="addDataDialog.data.type"
+          placeholder="字符串"
+          @change="addDataDialog.data.value = null"
+        />
+      </div>
+    </t-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { onBeforeMount, reactive, ref } from 'vue';
+import axios from 'axios';
+import { MessagePlugin } from 'tdesign-vue-next';
+
+import { importExcel } from '@/services/excel';
+import { typeOptions } from '@/services/common';
+
+const { modelValue } = defineProps<{
+  modelValue: any;
+}>();
+
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const datasetColumns = ref([
+  {
+    colKey: 'label',
+    title: '数据点名称',
+    ellipsis: true,
+  },
+  {
+    colKey: 'id',
+    title: '数据点ID',
+    ellipsis: true,
+  },
+  {
+    colKey: 'type',
+    title: '类型',
+    ellipsis: true,
+  },
+  {
+    colKey: 'actions',
+    title: '操作',
+    width: 100,
+  },
+]);
+
+const addDataDialog = reactive<any>({});
+
+onBeforeMount(() => {
+  if (!modelValue.data) {
+    modelValue.data = [];
+  }
+
+  getDatas();
+});
+
+const getDatas = async () => {
+  if (!modelValue.url) {
+    return;
+  }
+
+  const ret = await axios.get(modelValue.url);
+  if (ret) {
+    modelValue.data = ret;
+  }
+};
+
+const importDataset = async () => {
+  let columns: any = [
+    {
+      header: '数据点名称',
+      key: 'label',
+    },
+    {
+      header: '数据点ID',
+      key: 'id',
+    },
+    {
+      header: '类型',
+      key: 'type',
+    },
+  ];
+  const data: any = await importExcel(columns);
+  modelValue.data = data;
+  emit('update:modelValue', modelValue);
+  emit('change', modelValue);
+};
+
+const showAddData = (row?: any, index?: number) => {
+  if (row) {
+    addDataDialog.header = '编辑数据';
+    addDataDialog.data = JSON.parse(JSON.stringify(row));
+    addDataDialog.index = index;
+  } else {
+    addDataDialog.header = '添加数据';
+    addDataDialog.data = { type: 'string' };
+  }
+
+  addDataDialog.show = true;
+};
+
+const onOkAddData = () => {
+  if (!addDataDialog.data.label) {
+    MessagePlugin.error('请填写名称');
+    return;
+  }
+  if (!addDataDialog.data.id) {
+    MessagePlugin.error('请填写数据ID');
+    return;
+  }
+
+  if (addDataDialog.header === '添加数据') {
+    modelValue.data.push(addDataDialog.data);
+  } else {
+    modelValue.data[addDataDialog.index] = addDataDialog.data;
+  }
+
+  addDataDialog.show = false;
+};
+</script>
+<style lang="postcss" scoped>
+.dataset-component {
+  padding-right: 16px;
+}
+</style>

+ 10 - 4
src/views/components/Graphics.vue

@@ -507,8 +507,12 @@ const dragStart = async (event: DragEvent | MouseEvent, item: any) => {
   } else if (item['3d']) {
     data = {
       name: 'iframe',
-      width: 400,
-      height: 300,
+      x: 0,
+      y: 0,
+      tags: ['meta3d'],
+      zIndex: 1,
+      width: meta2d.store.data.width || meta2d.store.options.width,
+      height: meta2d.store.data.height || meta2d.store.options.height,
       externElement: true,
       iframe: 'https://view3d.le5le.com/?id=' + (item._id || item.id),
     };
@@ -1049,7 +1053,8 @@ onUnmounted(() => {
 .graphics {
   display: flex;
   flex-direction: column;
-
+  background-color: var(--color-background);
+  z-index: 2;
   .input-search {
     flex-shrink: 0;
     height: 40px;
@@ -1224,6 +1229,7 @@ onUnmounted(() => {
               stroke-width: 4px;
               fill: none;
               stroke-dasharray: none;
+              opacity: 1;
             }
           }
         }
@@ -1251,7 +1257,7 @@ onUnmounted(() => {
       .graphic {
         .t-image__wrapper {
           width: 100px;
-          height: 88px;
+          height: 56.25px;
           background-color: var(--color-background);
         }
       }

+ 7 - 6
src/views/components/Header.vue

@@ -154,7 +154,7 @@
           <a @click="onScaleDown">缩小</a>
         </t-dropdown-item>
         <t-dropdown-item divider="true">
-          <a @click="onScaleView">100%视图</a>
+          <a @click="onScaleFull">100%视图</a>
         </t-dropdown-item>
         <t-dropdown-item>
           <a @click="showMap">
@@ -317,7 +317,7 @@ import {
   blank,
   newFile,
   SaveType,
-  onScaleView,
+  onScaleFull,
   onScaleWindow,
   showMagnifier,
   showMap,
@@ -425,7 +425,7 @@ const openJson = async (file: File) => {
     }
     meta2d.open(data);
   } catch (e) {
-    console.log(e);
+    console.error(e);
   }
 };
 
@@ -838,9 +838,8 @@ const downloadImageTips =
   '无法下载,宽度不合法,画布可能没有画笔/画布大小超出浏览器最大限制';
 
 const downloadPng = () => {
-  const name = (meta2d.store.data as Meta2dBackData).name;
   try {
-    meta2d.downloadPng(name ? name + '.png' : undefined);
+    meta2d.downloadPng();
   } catch (e) {
     MessagePlugin.warning(downloadImageTips);
   }
@@ -1028,7 +1027,9 @@ const changeDisableAnchor = () => {
 .app-header {
   display: flex;
   height: 40px;
-
+  background-color: var(--color-background);
+  position: relative;
+  z-index: 2;
   .logo {
     display: flex;
     padding: 0 16px;

+ 79 - 36
src/views/components/Network.vue

@@ -1,30 +1,47 @@
 <template>
-  <div class="props">
+  <div class="network-component">
     <div class="form-item mt-8">
-      <label>数据源名称</label>
+      <label>
+        {{ modelValue.type === 'subscribe' ? '数据订阅' : '数据发送' }}
+      </label>
       <t-select-input
         v-if="mode"
         v-model:inputValue="modelValue.name"
         :value="modelValue.name"
-        placeholder="数据源"
+        placeholder="我的数据发送"
         allow-input
         clearable
         v-model:popup-visible="popupVisible"
         @focus="popupVisible = true"
         @blur="popupVisible = undefined"
         @input-change="onInput"
+        @clear="
+          modelValue.id = undefined;
+          modelValue._id = undefined;
+        "
       >
         <template #panel>
           <ul style="padding: 4px">
             <li
-              class="hover-background"
+              class="hover-background item"
               style="line-height: 1.5; padding: 8px; border-radius: 2px"
-              v-for="item in networkList"
+              v-for="(item, i) in networkList"
               :key="item.url"
               @click="() => onSelect(item)"
             >
               名称: {{ item.name }}
               <div class="desc">地址: {{ item.url }}</div>
+
+              <span class="del" @click.stop="onDelNetWork(item, i)">
+                <t-icon name="delete" />
+              </span>
+            </li>
+            <li
+              v-if="networkList.length >= 10"
+              style="line-height: 1.5; padding: 8px; border-radius: 2px"
+              :key="-1"
+            >
+              <div class="desc">...</div>
             </li>
             <li
               v-if="!networkList.length"
@@ -36,15 +53,15 @@
           </ul>
         </template>
       </t-select-input>
-      <t-input v-else v-model="modelValue.name" />
+      <t-input v-else v-model="modelValue.name" placeholder="名称" />
     </div>
 
     <div class="form-item mt-8">
-      <label>网络类型</label>
+      <label>通信方式</label>
       <t-select
-        v-model="modelValue.type"
+        v-model="modelValue.protocol"
         placeholder="MQTT"
-        @change="typeChange"
+        @change="protocolChange"
       >
         <t-option key="mqtt" value="mqtt" label="MQTT" />
         <t-option key="websocket" value="websocket" label="Websocket" />
@@ -55,13 +72,8 @@
       <label>URL地址</label>
       <t-input v-model="modelValue.url" />
     </div>
-    <template v-if="modelValue.type === 'websocket'">
-      <div class="form-item mt-8">
-        <label>Protocol</label>
-        <t-input v-model="modelValue.protocols" />
-      </div>
-    </template>
-    <template v-else-if="modelValue.type === 'http'">
+    <template v-if="modelValue.protocol === 'websocket'"> </template>
+    <template v-else-if="modelValue.protocol === 'http'">
       <div class="form-item mt-8">
         <label>请求方式</label>
         <t-select v-model="modelValue.method" @change="httpMethodChange">
@@ -72,7 +84,7 @@
       <div class="form-item mt-8">
         <label>请求头</label>
         <t-textarea
-          v-model="modelValue.httpHeaders"
+          v-model="modelValue.headers"
           :autosize="{ minRows: 3, maxRows: 5 }"
           placeholder="请输入"
         />
@@ -94,7 +106,7 @@
       <div class="form-item mt-8">
         <label>自动生成</label>
         <t-switch
-          class="ml-8 mt-8"
+          class="mt-8 ml-8"
           v-model="modelValue.options.customClientId"
           size="small"
         />
@@ -115,7 +127,7 @@
     <div class="form-item mt-8" v-if="mode">
       <label> </label>
       <div>
-        <t-button @click="onSave">保存到我的数据</t-button>
+        <t-button @click="onSave">保存到我的数据发送</t-button>
       </div>
     </div>
   </div>
@@ -125,6 +137,7 @@
 import { onBeforeMount, ref } from 'vue';
 import axios from 'axios';
 import { debounce } from '@/services/debouce';
+import { MessagePlugin } from 'tdesign-vue-next';
 
 const { modelValue, mode } = defineProps<{
   modelValue: any;
@@ -136,19 +149,23 @@ const emit = defineEmits(['update:modelValue', 'change']);
 const popupVisible = ref<boolean>(false);
 const networkList = ref<any[]>([]);
 
-onBeforeMount(() => {});
+onBeforeMount(() => {
+  modelValue.id = modelValue.id || modelValue._id;
+  if (mode) {
+    getNetworks();
+  }
+});
 
-const typeChange = (t: string) => {
-  if (t === 'http') {
+const protocolChange = (protocol: string) => {
+  if (protocol === 'http') {
     Object.assign(modelValue, {
-      http: '',
       httpTimeInterval: 1000,
-      httpHeaders: '',
+      headers: '',
       method: 'GET',
       body: '',
     });
-  } else if (t === 'websocket') {
-    modelValue.protocols = '';
+  } else if (protocol === 'websocket') {
+    // modelValue.url = '';
   } else {
     Object.assign(modelValue, {
       options: {
@@ -171,14 +188,17 @@ const onSave = async () => {
   emit('update:modelValue', modelValue);
   emit('change', modelValue);
 
-  const id = modelValue._id || modelValue.id;
+  let ret: any;
 
   // 保存到我的数据源
-  if (id) {
-    modelValue._id = id;
-    await axios.post(`/api/data/datasources/update`, modelValue);
+  if (modelValue.id) {
+    ret = await axios.post(`/api/data/datasources/update`, modelValue);
   } else {
-    await axios.post(`/api/data/datasources/add`, modelValue);
+    ret = await axios.post(`/api/data/datasources/add`, modelValue);
+  }
+
+  if (ret) {
+    MessagePlugin.success('保存成功!');
   }
 };
 
@@ -188,13 +208,27 @@ const onInput = (text: string) => {
 
 // 请求我的数据源接口
 const getNetworks = async () => {
-  const ret: any = await axios.post(`/api/data/datasources/list`, {
-    q: {
-      name: modelValue.name,
+  const body: any = {
+    query: {
+      type: modelValue.type,
     },
     projection: { updatedAt: 0 },
+  };
+  if (modelValue.name) {
+    body.q = {
+      name: modelValue.name,
+    };
+  }
+  const ret: any = await axios.post(`/api/data/datasources/list`, body, {
+    params: {
+      current: 1,
+      pageSize: 10,
+    },
   });
-  if (ret) {
+  if (ret?.list) {
+    for (const item of ret.list) {
+      item.id = item.id || item._id;
+    }
     networkList.value = ret.list;
   }
 };
@@ -203,8 +237,17 @@ const onSelect = (item: any) => {
   Object.assign(modelValue, item);
   popupVisible.value = false;
 };
+
+const onDelNetWork = async (item: any, i: number) => {
+  const ret: any = await axios.post(`/api/data/datasources/delete`, {
+    id: item._id || item.id,
+  });
+  if (ret) {
+    networkList.value.splice(i, 1);
+  }
+};
 </script>
 <style lang="postcss" scoped>
-.props {
+.network-component {
 }
 </style>

+ 202 - 194
src/views/components/PenDatas.vue

@@ -17,18 +17,17 @@
           <label class="label">{{ item.label }}</label>
         </t-tooltip>
         <div class="value">
-          <t-tooltip :content="getBindsDesc(item)" placement="top">
-            <t-icon
-              name="link"
-              class="hover"
-              :class="{ primary: item.binds?.id }"
-              @click="onBind(item)"
-            />
-          </t-tooltip>
           <t-input
-            v-if="item.type === 'number'"
+            v-if="item.type === 'integer'"
+            v-model.number="pen[item.key]"
+            placeholder="整数"
+            @change="changeValue(item.key)"
+          />
+          <t-input-number
+            v-else-if="item.type === 'float'"
             v-model="pen[item.key]"
-            placeholder="数字"
+            placeholder="浮点数"
+            theme="normal"
             @change="changeValue(item.key)"
           />
           <t-switch
@@ -51,6 +50,15 @@
             placeholder="字符串"
             @change="changeValue(item.key)"
           />
+
+          <t-tooltip :content="getBindsDesc(item)" placement="top">
+            <t-icon
+              name="link"
+              class="hover ml-4"
+              :class="{ primary: item.enableMock || item.bind?.id }"
+              @click="onBind(item)"
+            />
+          </t-tooltip>
         </div>
         <div>
           <t-tooltip :content="item.triggers?.length || '触发器'">
@@ -125,6 +133,7 @@
       <t-input
         v-model="addDataDialog.data.key"
         placeholder="关键字"
+        @blur="onKeyBlur"
         :disabled="!!addDataDialog.data.keywords"
       />
     </div>
@@ -136,120 +145,106 @@
         v-model="addDataDialog.data.type"
         placeholder="字符串"
         :disabled="!!addDataDialog.data.keywords"
-        @change="addDataDialog.data.value = null"
+        @change="onKeyBlur"
       />
     </div>
-    <div class="form-item mt-16">
-      <label>值</label>
-      <div class="flex-grow" v-if="addDataDialog.data.type === 'number'">
-        <t-input
-          class="w-full"
-          v-model="addDataDialog.data.value"
-          placeholder="数字"
-        />
-        <div class="desc mt-8">
-          固定数字:直接输入数字。例如:5<br />
-          随机范围数字 :最小值-最大值。例如:0-1 或 0-100
-          <br />
-          随机指定数字 :数字1,数字2,数字3... 。 例如:1,5,10,20<br />
-        </div>
-      </div>
-      <div class="flex-grow" v-else-if="addDataDialog.data.type === 'bool'">
-        <t-select v-model="addDataDialog.data.value">
-          <t-option :key="true" :value="true" label="true"></t-option>
-          <t-option :key="false" :value="false" label="false"></t-option>
-          <t-option key="随机" label="随机"></t-option>
-        </t-select>
-        <div class="desc mt-8">
-          固定:指定true或false<br />
-          随机:随机生成一个布尔值<br />
-        </div>
-      </div>
-      <div
-        class="flex-grow"
-        v-else-if="
-          addDataDialog.data.type === 'array' ||
-          addDataDialog.data.type === 'object'
-        "
-      >
-        <CodeEditor v-model="addDataDialog.data.value" :json="true" />
-      </div>
-      <div class="flex-grow" v-else>
-        <t-input
-          class="w-full"
-          v-model="addDataDialog.data.value"
-          placeholder="字符串"
-        />
-        <div class="desc mt-8">
-          固定文字:直接输入。例如:大屏可视化<br />
-          随机文本:[文本长度]。例如:[8] 或 [16]<br />
-          随机指定文本:{文本1,文本2,文本3...} 。 例如:{大屏, 可视化}
-          <br />
-        </div>
-      </div>
-    </div>
   </t-dialog>
 
   <t-dialog
     v-if="dataBindDialog.show"
     :visible="true"
     class="data-link-dialog"
-    header="动态数据绑定"
-    @close="
-      // dataBindDialog.data.binds = dataBindDialog.bkBinds;
-      dataBindDialog.show = false
-    "
-    @confirm="dataBindDialog.show = false"
+    header="动态数据设置"
+    @close="dataBindDialog.show = false"
+    @confirm="dataBindonConfirm"
+    :top="70"
     :width="700"
   >
-    <div class="form-item">
-      <label>当前绑定:</label>
-      <!-- <div class="label" v-if="dataBindDialog.data.binds?.length">
-        <t-tooltip
-          v-for="(tag, index) in dataBindDialog.data.binds"
-          :key="index"
-          :content="tag.id"
+    <t-tabs :defaultValue="1">
+      <t-tab-panel
+        :value="1"
+        label="绑定数据点"
+        :destroy-on-hide="false"
+        style="height: 420px"
+      >
+        <div class="form-item mt-12">
+          <label>当前绑定:</label>
+          <div class="label" v-if="dataBindDialog.data.bind?.id">
+            <t-tooltip :content="dataBindDialog.data.bind?.id">
+              <t-tag class="mr-8 mb-8" closable @close="onRemoveBind()">
+                {{ dataBindDialog.data.bind?.label }}
+              </t-tag>
+            </t-tooltip>
+          </div>
+          <div class="label gray" v-else>无</div>
+        </div>
+        <div class="form-item mt-8">
+          <t-input
+            placeholder="搜索"
+            v-model="dataBindDialog.input"
+            @change="onSearchDataset"
+            @enter="onSearchDataset"
+          >
+            <template #suffixIcon>
+              <t-icon name="search" class="hover" @click="onSearchDataset" />
+            </template>
+          </t-input>
+        </div>
+        <t-table
+          class="mt-12 data-list"
+          row-key="id"
+          :data="dataBindDialog.dataset"
+          :columns="dataSetColumns"
+          size="small"
+          bordered
+          :loading="dataBindDialog.loading"
+          :pagination="query"
+          @page-change="onChangePagination"
+          :selected-row-keys="dataBindDialog.selectedIds"
+          @select-change="onSelectBindsChange"
+          :max-height="270"
         >
-          <t-tag class="mr-8 mb-8" closable @close="onRemoveBind(index)">
-            {{ tag.label }}
-          </t-tag>
-        </t-tooltip>
-      </div> -->
-      <div class="label" v-if="dataBindDialog.data.binds?.id">
-        <t-tooltip :content="dataBindDialog.data.binds?.id">
-          <t-tag class="mr-8 mb-8" closable @close="onRemoveBind()">
-            {{ dataBindDialog.data.binds?.label }}
-          </t-tag>
-        </t-tooltip>
-      </div>
-      <div class="label gray" v-else>无</div>
-    </div>
-    <div class="form-item mt-8">
-      <t-input
-        placeholder="搜索"
-        v-model="dataBindDialog.input"
-        @change="onSearchDataset"
-        @enter="onSearchDataset"
+        </t-table>
+      </t-tab-panel>
+      <t-tab-panel
+        :value="2"
+        label="数据模拟"
+        :destroy-on-hide="false"
+        style="height: 420px"
       >
-        <template #suffixIcon>
-          <t-icon name="search" class="hover" @click="onSearchDataset" />
-        </template>
-      </t-input>
-    </div>
-    <t-table
-      class="mt-12 data-list"
-      row-key="id"
-      :data="dataBindDialog.dataSet"
-      :columns="dataSetColumns"
-      size="small"
-      bordered
-      :loading="dataBindDialog.loading"
-      :pagination="query"
-      @page-change="onChangePagination"
-      :selected-row-keys="dataBindDialog.selectedIds"
-      @select-change="onSelectBindsChange"
-    >
-    </t-table>
+        <div class="form-item mt-20">
+          <label>模拟值:</label>
+          <t-input v-model="dataBindDialog.data.mock" />
+        </div>
+        <div class="form-item mt-12">
+          <label>开启:</label>
+          <t-switch
+            class="mt-8"
+            size="small"
+            v-model="dataBindDialog.data.enableMock"
+          />
+        </div>
+        <h6 class="desc mt-20">模拟值说明</h6>
+        <ul class="desc mt-4">
+          <li class="mt-4">
+            <label class="inline" style="width: 80px">固定值:</label>
+            直接填写,例如:10
+          </li>
+          <li class="mt-4">
+            <label class="inline" style="width: 80px">随机值:</label>
+            {值1,值2,...}。例如:{1,2,3,4,5}
+          </li>
+          <li class="mt-4">
+            <label class="inline" style="width: 80px">范围数字:</label>
+            最小值-最大值。例如:0-1.0 或 0-100
+          </li>
+          <li class="mt-4">
+            <label class="inline" style="width: 80px">随机字符串:</label>
+            [长度]。例如:[8]
+          </li>
+        </ul>
+      </t-tab-panel>
+    </t-tabs>
   </t-dialog>
 
   <t-dialog
@@ -425,8 +420,10 @@ import {
 import { useRoute, useRouter } from 'vue-router';
 import { MessagePlugin } from 'tdesign-vue-next';
 import axios from 'axios';
+import { getter, setter } from '@meta2d/core/src/utils/object';
 import { debounce } from '@/services/debouce';
 import { getPenTree, typeOptions } from '@/services/common';
+import { searchPinyin } from '@/services/pinyin';
 import { updatePen } from './pen';
 
 import CodeEditor from '@/views/components/common/CodeEditor.vue';
@@ -482,7 +479,7 @@ const options = ref<any>([
   {
     value: 'text',
     content: '文字',
-    keywords: true,
+    // keywords: true,
   },
   {
     value: 'progress',
@@ -532,21 +529,21 @@ const dataSetColumns = [
     width: 50,
   },
   {
-    colKey: 'id',
-    title: '编号',
-    width: 150,
+    colKey: 'label',
+    title: '数据点名称',
+    width: 200,
     ellipsis: { theme: 'light', trigger: 'context-menu' },
   },
   {
-    colKey: 'label',
-    title: '动态数据名称',
-    width: 220,
+    colKey: 'id',
+    title: '数据点ID',
+    width: 200,
     ellipsis: { theme: 'light', trigger: 'context-menu' },
   },
   {
-    colKey: 'case',
-    title: '场景',
-    ellipsis: { theme: 'light', trigger: 'context-menu' },
+    colKey: 'type',
+    title: '类型',
+    width: 100,
   },
 ];
 
@@ -621,6 +618,17 @@ const addRealTime = (e: any) => {
   addDataDialog.show = true;
 };
 
+const onKeyBlur = () => {
+  if (addDataDialog.data) {
+    let value = getter(props.pen, addDataDialog.data.key);
+    if (value) {
+      setter(addDataDialog.data, 'value', value);
+    } else {
+      addDataDialog.data.value = null;
+    }
+  }
+};
+
 const onChangeLabel = () => {
   if (!addDataDialog.data.key) {
     addDataDialog.data.key = addDataDialog.data.label;
@@ -647,8 +655,6 @@ const onConfirmData = () => {
     props.pen.realTimes.push(addDataDialog.data);
   }
 
-  meta2d.penMock(props.pen);
-
   addDataDialog.show = false;
 };
 
@@ -656,6 +662,7 @@ const onMenuMore = (e: any, item: any, i: number) => {
   switch (e.value) {
     case 'edit':
       addDataDialog.header = '编辑动态数据';
+      setter(item, 'value', getter(props.pen, item.key));
       addDataDialog.data = item;
       addDataDialog.show = true;
       break;
@@ -668,23 +675,11 @@ const onMenuMore = (e: any, item: any, i: number) => {
 };
 
 const onBind = (item: any) => {
-  // if (!item.binds) {
-  //   item.binds = [];
-  // }
-  // dataBindDialog.data = item;
-  // dataBindDialog.input = '';
-  // dataBindDialog.selectedIds = [];
-  // for (const i of item.binds) {
-  //   dataBindDialog.selectedIds.push(i.id);
-  // }
-  // dataBindDialog.bkBinds = [];
-  // dataBindDialog.bkBinds.push(...item.binds);
-  // dataBindDialog.show = true;
   dataBindDialog.data = item;
   dataBindDialog.input = '';
   dataBindDialog.selectedIds = [];
-  if (item.binds && item.binds.id) {
-    dataBindDialog.selectedIds.push(item.binds.id);
+  if (item.bind && item.bind.id) {
+    dataBindDialog.selectedIds.push(item.bind.id);
   }
   dataBindDialog.show = true;
   getDataset();
@@ -698,15 +693,33 @@ const getDataset = async () => {
   // @ts-ignore
   const data: Meta2dBackData = meta2d.data();
 
-  dataBindDialog.loading = true;
-
-  // 应该从data获取url或结果列表
-  const ret: any = await axios.get(
-    `/api/device/data/set?mock=1&q=${dataBindDialog.input}&current=${query.current}&pageSize=${query.pageSize}`
-  );
+  if (!data.dataset) {
+    return;
+  }
 
-  dataBindDialog.dataSet = ret.list;
-  query.total = ret.total;
+  dataBindDialog.loading = true;
+  if (data.dataset.url) {
+    const ret: any = await axios.get(data.dataset.url, {
+      params: {
+        q: dataBindDialog.input,
+        current: query.current,
+        pageSize: query.pageSize,
+      },
+    });
+    dataBindDialog.dataset = ret;
+    query.total = ret.total;
+  } else if (dataBindDialog.input) {
+    dataBindDialog.dataset = data.dataset.data.filter((item: any) => {
+      return (
+        searchPinyin(item.label, dataBindDialog.input) ||
+        item.id.indexOf(dataBindDialog.input) > -1
+      );
+    });
+    query.total = dataBindDialog.dataset.length;
+  } else {
+    dataBindDialog.dataset = data.dataset.data;
+    query.total = dataBindDialog.dataset.length;
+  }
   dataBindDialog.loading = false;
 };
 
@@ -721,65 +734,55 @@ const onChangePagination = (pageInfo: any) => {
 };
 
 const onSelectBindsChange = (value: string[], options: any) => {
-  // dataBindDialog.selectedIds = value;
-
   if (options.type === 'check') {
-    // for (const item of options.selectedRowData) {
-    //   const found = dataBindDialog.data.binds.findIndex((elem: any) => {
-    //     return elem.id === item.id;
-    //   });
-    //   if (found < 0) {
-    //     dataBindDialog.data.binds.push(toRaw(item));
-    //   }
-    // }
-
     dataBindDialog.selectedIds = value;
-    dataBindDialog.data.binds = toRaw(options.selectedRowData[0]);
+    dataBindDialog.data.bind = toRaw(options.selectedRowData[0]);
+    doBindInit();
   } else if (options.type === 'uncheck') {
-    // if (options.currentRowKey === 'CHECK_ALL_BOX') {
-    //   for (const data of dataBindDialog.dataSet) {
-    //     const found = dataBindDialog.data.binds.findIndex((elem: any) => {
-    //       return elem.id === data.id;
-    //     });
-    //     if (found > -1) {
-    //       dataBindDialog.data.binds.splice(found, 1);
-    //     }
-    //   }
-    // } else {
-    //   const found = dataBindDialog.data.binds.findIndex((elem: any) => {
-    //     return elem.id === options.currentRowKey;
-    //   });
-    //   if (found > -1) {
-    //     dataBindDialog.data.binds.splice(found, 1);
-    // }
     dataBindDialog.selectedIds = [];
-    dataBindDialog.data.binds = {};
-    // }
+    dataBindDialog.data.bind = {};
   }
 };
 
-const onRemoveBind = () => {
-  // dataBindDialog.data.binds.splice(index, 1);
+const doBindInit = () => {
+  let { id } = dataBindDialog.data;
+  if (props.pen.name === 'echarts' && id.includes('echarts.option.series')) {
+    const { replaceMode } = props.pen.echarts;
+    const { xAxis } = props.pen.echarts.option;
+
+    let beforeV = getter(props.pen, id);
+    if (Array.isArray(beforeV) && replaceMode === 0) {
+      //追加
+      setter(props.pen, id, []);
+      let _key = 'echarts.option.xAxis.data';
+      if (Array.isArray(xAxis) && xAxis.length) {
+        _key = 'echarts.option.xAxis.0.data';
+      }
+      setter(props.pen, _key, []);
+    }
+  }
+};
 
+const dataBindonConfirm = () => {
+  dataBindDialog.show = false;
+  meta2d.initBinds();
+};
+
+const onRemoveBind = () => {
   dataBindDialog.selectedIds = [];
-  // for (const i of dataBindDialog.data.binds) {
-  //   dataBindDialog.selectedIds.push(i.id);
-  // }
-  dataBindDialog.data.binds = undefined;
+  dataBindDialog.data.bind = undefined;
 };
 
 const getBindsDesc = (item: any) => {
-  if (!item.binds || !item.binds.id) {
-    return '绑定动态数据';
+  if (item.bind?.label) {
+    return item.bind.label;
+  }
+
+  if (item.enableMock && item.mock) {
+    return item.mock;
   }
-  // let desc = '';
-  // for (const i of item.binds) {
-  //   desc += i.label + ',';
-  // }
-  // if (desc && desc.length > 1) {
-  //   desc = desc.substring(0, desc.length - 1);
-  // }
-  return item.binds.label;
+
+  return '动态数据';
 };
 
 const changeValue = (prop: string) => {
@@ -949,8 +952,13 @@ onUnmounted(() => {
   }
 
   .data-list {
-    height: 300px;
-    overflow: auto;
+    :deep(.t-table__header--fixed:not(.t-table__header--multiple) > tr > th) {
+      background: none;
+    }
+
+    :deep(.t-table__pagination) {
+      padding-bottom: 0;
+    }
   }
 }
 

+ 51 - 1
src/views/components/PenProps.vue

@@ -858,10 +858,13 @@
                     v-model="data.pen[item.key]"
                     @change="changeValue(item.key)"
                   />
-                  <t-input
+                  <t-input-number
                     class="w-full"
                     v-else-if="item.type === 'number'"
                     v-model.number="data.pen[item.key]"
+                    theme="column"
+                    :max="item.max"
+                    :min="item.min"
                     @change="changeValue(item.key)"
                     :placeholder="item.placeholder"
                   />
@@ -884,6 +887,16 @@
                     @change="changeValue(item.key)"
                     :placeholder="item.placeholder"
                   />
+                  <t-button
+                    v-else-if="item.type === 'code'"
+                    shape="square"
+                    variant="outline"
+                    style="width: 24px"
+                    @click="showPropsEdit(item)"
+                  >
+                    <t-icon name="ellipsis" slot="icon"
+                  /></t-button>
+
                   <t-input
                     class="w-full"
                     v-else
@@ -990,6 +1003,25 @@
             </div>
             <div class="gray" style="font-size: 12px">支持Markdown格式</div>
           </t-dialog>
+          <t-dialog
+            v-if="propsDialog.show"
+            :visible="true"
+            :header="propsDialog.header"
+            @confirm="onOkPropsEdit"
+            @close="propsDialog.show = false"
+            :width="700"
+          >
+            <div class="py-8">
+              <CodeEditor
+                :json="true"
+                v-model="propsDialog.value"
+                style="height: 300px"
+              />
+            </div>
+            <div class="gray" style="font-size: 12px">
+              {{ propsDialog.placeholder }}
+            </div>
+          </t-dialog>
           <t-space />
         </t-space>
       </t-tab-panel>
@@ -1043,6 +1075,10 @@ const tooltipDialog = reactive<any>({
   show: false,
 });
 
+const propsDialog = reactive<any>({
+  show: false,
+});
+
 const iconsDrawer = reactive<any>({
   show: false,
 });
@@ -1216,6 +1252,20 @@ const onOkTooltip = () => {
   tooltipDialog.show = false;
 };
 
+const showPropsEdit = (item: any) => {
+  propsDialog.key = item.key;
+  propsDialog.header = `${item.label}(${item.key})`;
+  propsDialog.value = data.pen[item.key];
+  propsDialog.placeholder = item.placeholder;
+  propsDialog.show = true;
+};
+
+const onOkPropsEdit = () => {
+  data.pen[propsDialog.key] = propsDialog.value;
+  updatePen(data.pen, propsDialog.key);
+  propsDialog.show = false;
+};
+
 const onChangeIcon = (params: any) => {
   Object.assign(data.pen, params);
   meta2d.setValue({

+ 364 - 284
src/views/components/View.vue

@@ -87,7 +87,7 @@
         <a
           :draggable="true"
           @dragstart="onAddShape($event, 'line')"
-          @click.stop="onAddShape($event, 'line')"
+          @click="onAddShape($event, 'line')"
           ><t-icon name="slash"
         /></a>
       </t-tooltip>
@@ -95,7 +95,7 @@
         <a
           :draggable="true"
           @dragstart="onAddShape($event, 'text')"
-          @click.stop="onAddShape($event, 'text')"
+          @click="onAddShape($event, 'text')"
           >T</a
         >
       </t-tooltip>
@@ -210,7 +210,7 @@
       </t-tooltip>
 
       <t-tooltip content="100%视图" placement="bottom">
-        <a @click="onScaleView"><t-icon name="refresh" /></a>
+        <a @click="onScaleFull"><t-icon name="refresh" /></a>
       </t-tooltip>
       <t-tooltip content="窗口大小" placement="bottom">
         <a @click="onScaleWindow"><t-icon name="fullscreen-exit" /></a>
@@ -285,15 +285,23 @@
       :visible="true"
       @close="dataDialog.show = false"
     >
-      <t-tabs :default-value="1" class="body">
+      <t-tabs v-model="dataDialog.tab" class="body">
         <t-tab-panel :value="1" label="数据订阅" :destroy-on-hide="false">
           <template #panel>
             <div v-if="!dataDialog.editNetwork">
-              <t-row class="mt-16" justify="end">
-                <t-space :size="12">
+              <div class="mt-16 flex between middle">
+                <div>
+                  <t-checkbox
+                    v-model="dataDialog.enableMock"
+                    @change="onChangeMock"
+                  >
+                    开启模拟数据
+                  </t-checkbox>
+                </div>
+                <div>
                   <t-select-input
                     v-model:inputValue="dataDialog.input"
-                    placeholder="搜索我的数据源"
+                    placeholder="搜索我的数据订阅"
                     allow-input
                     clearable
                     :popup-visible="dataDialog.popupVisible"
@@ -305,18 +313,27 @@
                     <template #panel>
                       <ul style="padding: 4px">
                         <li
-                          class="hover-background"
+                          class="hover-background item"
+                          v-for="(item, i) in dataDialog.networkList"
+                          :key="item.url"
+                          @click="onSelectNetWork(item)"
+                        >
+                          名称: {{ item.name }}
+                          <div class="desc">地址: {{ item.url }}</div>
+                          <span class="del" @click.stop="onDelNetWork(item, i)">
+                            <t-icon name="delete" />
+                          </span>
+                        </li>
+                        <li
+                          v-if="dataDialog.networkList.length >= 10"
                           style="
                             line-height: 1.5;
                             padding: 8px;
                             border-radius: 2px;
                           "
-                          v-for="item in dataDialog.networkList"
-                          :key="item.url"
-                          @click="() => onSelectNetWork(item)"
+                          :key="-1"
                         >
-                          名称: {{ item.name }}
-                          <div class="desc">地址: {{ item.url }}</div>
+                          <div class="desc">...</div>
                         </li>
                         <li
                           v-if="!dataDialog.networkList.length"
@@ -335,11 +352,15 @@
                       <t-icon name="search" class="hover" />
                     </template>
                   </t-select-input>
-                  <t-button style="height: 30px" @click="addNetwork">
+                  <t-button
+                    class="ml-12"
+                    style="height: 30px"
+                    @click="addNetwork"
+                  >
                     添加数据订阅
                   </t-button>
-                </t-space>
-              </t-row>
+                </div>
+              </div>
               <t-table
                 class="mt-12"
                 row-key="id"
@@ -348,17 +369,12 @@
                 :max-height="280"
                 size="small"
               >
-                <template #type="{ row, rowIndex }">
-                  {{ row.type || 'MQTT' }}
+                <template #protocol="{ row, rowIndex }">
+                  {{ row.protocol || 'MQTT' }}
                 </template>
                 <template #actions="{ row, rowIndex }">
                   <a @click="editNetwork(row)"> 编辑 </a>
-                  <t-popconfirm
-                    content="确认删除吗"
-                    @confirm="deleteNetwork(rowIndex)"
-                  >
-                    <a class="ml-12"> 删除 </a>
-                  </t-popconfirm>
+                  <a class="ml-12" @click="deleteNetwork(rowIndex)"> 删除 </a>
                 </template>
                 <template #empty>
                   <div class="center">
@@ -373,7 +389,10 @@
             <div v-else>
               <div class="mt-8">
                 <a
-                  @click="dataDialog.editNetwork = false"
+                  @click="
+                    dataDialog.network = dataDialog.networkBak;
+                    dataDialog.editNetwork = false;
+                  "
                   class="flex middle"
                   style="width: 44px"
                 >
@@ -387,173 +406,131 @@
           </template>
         </t-tab-panel>
         <t-tab-panel :value="2" :destroy-on-hide="false">
-          <template #label>
-            数据集
-            <span><label class="vip-label ml-4">VIP</label></span>
-          </template>
+          <template #label> 数据集 </template>
           <template #panel>
-            <div class="form-item mt-20">
-              <label style="width: 100px">
-                网络地址
-                <t-tooltip
-                  content="使用网络数据代替自定义数据。高优先级,生产环境使用"
-                >
-                  <t-icon
-                    name="help-circle"
-                    class="ml-4 hover"
-                    style="margin-top: -2px"
-                  />
-                </t-tooltip>
-              </label>
-              <div class="w-full">
-                <t-input v-model="dataDialog.datasetUrl" />
+            <template v-if="!dataDialog.editDataset">
+              <div class="form-item mt-16">
+                <label style="width: 100px"> 当前数据集 </label>
+                <div class="flex w-full">
+                  <t-select
+                    class="flex-grow"
+                    v-model="dataDialog.datasetId"
+                    filterable
+                    placeholder="选择数据集"
+                    :on-search="onInputDatasets"
+                    :popup-props="{ overlayClassName: 'select-options' }"
+                    @change="onSelDataset()"
+                  >
+                    <t-option
+                      v-for="(item, i) in dataDialog.datasetList"
+                      :key="item.id"
+                      :value="item.id"
+                      :label="item.name"
+                    >
+                      <div class="hover-background item">
+                        名称: {{ item.name }}
+                        <div v-if="item.url" class="desc">
+                          URL: {{ item.url }}
+                        </div>
+                        <div v-else class="desc">自定义</div>
+                        <span class="del" @click.stop="onDelDataset(item, i)">
+                          <t-icon name="delete" />
+                        </span>
+                      </div>
+                    </t-option>
+                    <t-option
+                      v-if="dataDialog.datasetList.length >= 10"
+                      :disabled="true"
+                    >
+                      <div class="ml-8 gray">...</div>
+                    </t-option>
+                  </t-select>
+
+                  <t-button
+                    class="ml-12 shrink-0"
+                    style="height: 30px"
+                    @click="addDataset"
+                  >
+                    添加数据集
+                  </t-button>
+                </div>
               </div>
-            </div>
-            <div class="form-item" style="margin-top: 28px">
-              <label style="width: 100px">
-                自定义
-                <t-tooltip content="初始静态或模拟数据,开发设计阶段使用">
-                  <t-icon
-                    name="help-circle"
-                    class="ml-4 hover"
-                    style="margin-top: -2px"
-                  />
-                </t-tooltip>
-              </label>
-              <div class="w-full flex">
-                <t-button @click="importDataset">从Excel导入</t-button>
-                <a href="/data.xlsx" download class="ml-16 mt-4">
-                  下载Excel示例
+
+              <t-table
+                class="mt-12"
+                row-key="id"
+                :data="dataDialog.dataset.data"
+                :columns="datasetColumns"
+                size="small"
+                :max-height="280"
+              >
+                <template #type="{ row }">
+                  {{ row.type || 'string' }}
+                </template>
+              </t-table>
+            </template>
+            <div v-else>
+              <div class="mt-8">
+                <a
+                  @click="
+                    dataDialog.dataset = dataDialog.datasetBak;
+                    dataDialog.datasetId = dataDialog.dataset.id;
+                    dataDialog.editDataset = 0;
+                  "
+                  class="flex middle"
+                  style="width: 44px"
+                >
+                  <t-icon name="rollback" class="mr-4" /> 返回
                 </a>
-                <div class="flex-grow"></div>
-                <a class="mt-4" @click="showAddData()"> + 添加数据 </a>
+              </div>
+              <div style="height: 300px; overflow-y: auto">
+                <Dataset v-model="dataDialog.dataset" />
               </div>
             </div>
-
-            <t-table
-              class="mt-12"
-              row-key="id"
-              :data="dataDialog.dataset"
-              :columns="datasetColumns"
-              size="small"
-              :max-height="210"
-            >
-              <template #label="{ row }">
-                {{ `${row.label}(${row.key})` }}
-              </template>
-              <template #type="{ row }">
-                {{ row.type || 'string' }}
-              </template>
-              <template #actions="{ row, rowIndex }">
-                <t-icon name="edit" class="hover" @click="showAddData(row)" />
-                <t-icon
-                  name="delete"
-                  class="ml-12 hover"
-                  @click="dataDialog.dataset.splice(rowIndex, 1)"
-                />
-              </template>
-              <template #empty>
-                <div class="center">
-                  暂无数据, <a class="mt-4" @click="showAddData()"> + 添加 </a>
-                </div>
-              </template>
-            </t-table>
           </template>
         </t-tab-panel>
       </t-tabs>
 
       <template #footer>
-        <div v-if="dataDialog.editNetwork" class="flex middle">
+        <div
+          v-if="dataDialog.tab === 1 && dataDialog.editNetwork"
+          class="flex middle"
+        >
           <div class="flex-grow"></div>
           <t-checkbox v-model="dataDialog.save">
-            同时保存到我的数据源
+            同时保存到我的数据订阅
           </t-checkbox>
-          <t-button class="ml-16" @click="onOkNetwork">确定</t-button>
+          <t-button @click="onOkNetwork">确定</t-button>
+        </div>
+        <div v-else-if="dataDialog.tab === 2" class="flex middle">
+          <template v-if="dataDialog.editDataset === 1">
+            <div class="flex-grow"></div>
+            <t-checkbox v-model="dataDialog.save">
+              同时保存为我的数据集
+            </t-checkbox>
+            <t-button @click="onOkDataset()">保存</t-button>
+          </template>
+          <template v-else-if="dataDialog.editDataset === 2">
+            <div class="flex-grow"></div>
+            <t-button @click="onOkDataset(true)"> 另外为新数据集 </t-button>
+            <t-button @click="onOkDataset()">保存</t-button>
+          </template>
+          <template v-else>
+            <a v-if="dataDialog.dataset.id" @click="editDataset">
+              编辑当前数据集
+            </a>
+            <div class="flex-grow"></div>
+
+            <t-button @click="dataDialog.show = false"> 完成 </t-button>
+          </template>
         </div>
         <div v-else class="flex middle">
           <div class="flex-grow"></div>
-          <t-button class="ml-16" @click="onOkDatasources"> 完成 </t-button>
+          <t-button @click="dataDialog.show = false"> 完成 </t-button>
         </div>
       </template>
     </t-dialog>
 
-    <t-dialog
-      v-if="addDataDialog.show"
-      :visible="true"
-      class="data-dialog"
-      :header="addDataDialog.header"
-      @close="addDataDialog.show = false"
-      @confirm="onOkAddData"
-    >
-      <div class="form-item mt-16">
-        <label>名称</label>
-        <t-input v-model="addDataDialog.data.label" placeholder="简短描述" />
-      </div>
-      <div class="form-item mt-16">
-        <label>数据ID</label>
-        <t-input v-model="addDataDialog.data.key" placeholder="数据ID" />
-      </div>
-      <div class="form-item mt-16">
-        <label>类型</label>
-        <t-select
-          class="w-full"
-          :options="typeOptions"
-          v-model="addDataDialog.data.type"
-          placeholder="字符串"
-          @change="addDataDialog.data.value = null"
-        />
-      </div>
-      <div class="form-item mt-16">
-        <label>值</label>
-        <div class="flex-grow" v-if="addDataDialog.data.type === 'number'">
-          <t-input
-            class="w-full"
-            v-model="addDataDialog.data.value"
-            placeholder="数字"
-          />
-          <div class="desc mt-8">
-            固定数字:直接输入数字。例如:5<br />
-            随机范围数字 :最小值-最大值。例如:0-1 或 0-100
-            <br />
-            随机指定数字 :数字1,数字2,数字3... 。 例如:1,5,10,20<br />
-          </div>
-        </div>
-        <div class="flex-grow" v-else-if="addDataDialog.data.type === 'bool'">
-          <t-select v-model="addDataDialog.data.value">
-            <t-option :key="true" :value="true" label="true"></t-option>
-            <t-option :key="false" :value="false" label="false"></t-option>
-            <t-option key="随机" label="随机"></t-option>
-          </t-select>
-          <div class="desc mt-8">
-            固定:指定true或false<br />
-            随机:随机生成一个布尔值<br />
-          </div>
-        </div>
-        <div
-          class="flex-grow"
-          v-else-if="
-            addDataDialog.data.type === 'array' ||
-            addDataDialog.data.type === 'object'
-          "
-        >
-          <CodeEditor v-model="addDataDialog.data.value" :json="true" />
-        </div>
-        <div class="flex-grow" v-else>
-          <t-input
-            class="w-full"
-            v-model="addDataDialog.data.value"
-            placeholder="字符串"
-          />
-          <div class="desc mt-8">
-            固定文字:直接输入。例如:大屏可视化<br />
-            随机文本:[文本长度]。例如:[8] 或 [16]<br />
-            随机指定文本:{文本1,文本2,文本3...} 。 例如:{大屏, 可视化}
-            <br />
-          </div>
-        </div>
-      </div>
-    </t-dialog>
-
     <t-dialog
       v-if="publishDialog.show"
       width="700px"
@@ -734,23 +711,21 @@ import {
   autoSave,
   newFile,
   SaveType,
-  onScaleView,
+  onScaleFull,
   onScaleWindow,
   useDot,
-  typeOptions,
 } from '@/services/common';
 import { useSelection } from '@/services/selections';
 import { defaultFormat } from '@/services/defaults';
 import { checkData, localStorageName, Meta2dBackData } from '@/services/utils';
 import { debounce } from '@/services/debouce';
-import { importExcel } from '@/services/excel';
 import { s8 } from '@/services/random';
+import { setCookie } from '@/services/cookie';
 
-import CodeEditor from './common/CodeEditor.vue';
 import ContextMenu from './ContextMenu.vue';
 import Network from './Network.vue';
+import Dataset from './Dataset.vue';
 import ChargeCloudPublish from './ChargeCloudPublish.vue';
-import { setCookie } from '@/services/cookie';
 
 const router = useRouter();
 const route = useRoute();
@@ -768,7 +743,6 @@ const meta2dOptions: Options = {
   height: 1080,
   color: '#bdc7db',
   disableAnchor: true,
-  domShapes: ['threeDSence'],
   defaultFormat: { ...defaultFormat },
 };
 
@@ -784,15 +758,13 @@ const publishDialog = reactive<any>({});
 
 const publishChargeDialog = reactive<any>({});
 
-const addDataDialog = reactive<any>({});
-
 onMounted(() => {
   meta2d = new Meta2d('meta2d', meta2dOptions);
   registerBasicDiagram();
   open(true);
   meta2d.on('active', active);
   meta2d.on('inactive', inactive);
-  meta2d.on('scale', scaleListener);
+  meta2d.on('scale', scaleSubscriber);
   meta2d.on('add', lineAdd);
   meta2d.on('opened', openedListener);
 
@@ -886,20 +858,6 @@ onUnmounted(() => {
   clearInterval(timer);
   watcher();
   if (meta2d) {
-    meta2d.off('active', active);
-    meta2d.off('inactive', inactive);
-    meta2d.off('scale', scaleListener);
-    meta2d.off('add', lineAdd);
-    meta2d.on('opened', openedListener);
-    meta2d.off('undo', patchFlag);
-    meta2d.off('redo', patchFlag);
-    meta2d.off('add', patchFlag);
-    meta2d.off('delete', patchFlag);
-    meta2d.off('rotatePens', patchFlag);
-    meta2d.off('translatePens', patchFlag);
-    meta2d.off('components-update-value', patchFlag);
-    meta2d.off('contextmenu', onContextmenu);
-    meta2d.off('click', canvasClick);
     meta2d.destroy();
   }
 });
@@ -945,11 +903,13 @@ const clearFormat = () => {
 };
 
 const scale = ref(100);
-function scaleListener(newScale: number) {
-  scale.value = Math.round(newScale * 100);
-}
+const scaleSubscriber = (val: number) => {
+  scale.value = Math.round(val * 100);
+};
 
-const dataDialog = reactive<any>({});
+const dataDialog = reactive<any>({
+  tab: 1,
+});
 
 const currentLineType = ref('curve');
 const lineTypes = reactive([
@@ -1091,6 +1051,7 @@ const lineAdd = (pens: Pen[]) => {
 };
 
 const onAddShape = (event: DragEvent | MouseEvent, name: string) => {
+  event.stopPropagation();
   let data: any;
   if (name === 'text') {
     data = {
@@ -1102,11 +1063,11 @@ const onAddShape = (event: DragEvent | MouseEvent, name: string) => {
   } else if (name === 'line') {
     data = {
       anchors: [
-        { id: '0', x: 0, y: 0.5 },
-        { id: '1', x: 1, y: 0.5 },
+        { id: '0', x: 1, y: 0 },
+        { id: '1', x: 0, y: 1 },
       ],
       width: 100,
-      height: 1,
+      height: 100,
       name: 'line',
       lineName: 'line',
       type: 1,
@@ -1117,7 +1078,6 @@ const onAddShape = (event: DragEvent | MouseEvent, name: string) => {
   } else {
     (event as DragEvent).dataTransfer?.setData('Meta2d', JSON.stringify(data));
   }
-  event.stopPropagation();
 };
 
 const isLock = ref(0);
@@ -1205,8 +1165,8 @@ const networkColumns = ref([
     width: 160,
   },
   {
-    colKey: 'type',
-    title: '类型',
+    colKey: 'protocol',
+    title: '通信方式',
     width: 120,
   },
   {
@@ -1217,46 +1177,126 @@ const networkColumns = ref([
   { colKey: 'actions', title: '操作', width: 100 },
 ]);
 
-const onShowDataDialog = () => {
+const onShowDataDialog = async () => {
   dataDialog.input = '';
   dataDialog.networks = meta2d.store.data.networks || [];
   // @ts-ignore
-  dataDialog.datasetUrl = meta2d.store.data.datasetUrl;
+  dataDialog.dataset = meta2d.store.data.dataset || {};
+  dataDialog.datasetId = dataDialog.dataset.id;
   // @ts-ignore
-  dataDialog.dataset = meta2d.store.data.dataset;
+  dataDialog.enableMock = meta2d.store.data.enableMock;
   dataDialog.networkList = [];
+  dataDialog.datasetList = [];
   dataDialog.editNetwork = false;
   dataDialog.save = true;
   dataDialog.show = true;
   getNetworks();
+  await getDatasets();
+  onSelDataset(true);
+};
+
+const onChangeMock = () => {
+  // @ts-ignore
+  meta2d.store.data.enableMock = dataDialog.enableMock;
 };
 
 const onSelectNetWork = (item: any) => {
-  dataDialog.networks.push(item);
+  const network: any = dataDialog.networks.find(
+    (elem: any) => item.id === elem.id
+  );
+  if (!network) {
+    dataDialog.networks.push(item);
+    meta2d.store.data.networks = dataDialog.networks;
+    meta2d.connectNetwork();
+    setDot(true);
+  }
+
   dataDialog.popupVisible = false;
 };
 
-const onInputNetwork = (text: string) => {
+const onDelNetWork = async (item: any, i: number) => {
+  const ret: any = await axios.post(`/api/data/datasources/delete`, {
+    id: item._id || item.id,
+  });
+  if (ret) {
+    dataDialog.networkList.splice(i, 1);
+  }
+};
+
+const onInputNetwork = () => {
   debounce(getNetworks, 300);
 };
 
-// 请求我的数据源接口
+// 请求我的数据订阅
 const getNetworks = async () => {
-  const ret: any = await axios.post(`/api/data/datasources/list`, {
-    q: {
-      name: dataDialog.input,
+  const ret: any = await axios.post(
+    `/api/data/datasources/list`,
+    {
+      q: {
+        name: dataDialog.input,
+      },
+      query: {
+        type: 'subscribe',
+      },
+      projection: { updatedAt: 0 },
+    },
+    {
+      params: {
+        current: 1,
+        pageSize: 10,
+      },
+    }
+  );
+  if (ret?.list) {
+    for (const item of ret.list) {
+      item.id = item.id || item._id;
+    }
+    dataDialog.networkList = ret.list;
+  }
+};
+
+const onInputDatasets = (name: string) => {
+  debounce(getDatasets, 300, name);
+};
+
+// 请求我的数据集
+const getDatasets = async (name?: string) => {
+  const body: any = {
+    query: {
+      type: 'dataset',
     },
     projection: { updatedAt: 0 },
+  };
+  if (name) {
+    body.q = { name };
+  }
+  const ret: any = await axios.post(`/api/data/datasources/list`, body, {
+    params: {
+      current: 1,
+      pageSize: 10,
+    },
   });
-  if (ret) {
-    dataDialog.networkList = ret.list;
+  if (ret?.list) {
+    let found = false;
+    for (const item of ret.list) {
+      item.id = item.id || item._id;
+      if (dataDialog.dataset?.id === item.id) {
+        found = true;
+      }
+    }
+    if (dataDialog.dataset?.id && !found) {
+      ret.list.push(dataDialog.dataset);
+    }
+
+    dataDialog.datasetList = ret.list;
   }
 };
 
 const addNetwork = () => {
   dataDialog.network = {
     name: '',
-    type: '',
+    type: 'subscribe',
+    protocol: 'mqtt',
     url: '',
     options: {
       clientId: '',
@@ -1269,18 +1309,19 @@ const addNetwork = () => {
 };
 
 const editNetwork = (data: any) => {
-  dataDialog.network = data;
+  dataDialog.networkBak = data;
+  dataDialog.network = JSON.parse(JSON.stringify(data));
   dataDialog.editNetwork = 2;
 };
 
 const deleteNetwork = (index: number) => {
   dataDialog.networks.splice(index, 1);
-  meta2d.connectSocket();
+  meta2d.store.data.networks = dataDialog.networks;
+  meta2d.connectNetwork();
+  setDot(true);
 };
 
 const onOkNetwork = async () => {
-  // 保存到我的数据源
-
   if (dataDialog.editNetwork === 1) {
     if (dataDialog.save) {
       const ret: any = await axios.post(
@@ -1290,10 +1331,11 @@ const onOkNetwork = async () => {
       if (!ret) {
         return;
       }
-      dataDialog.network._id = ret._id || ret.id;
-      dataDialog.network.id = dataDialog.network._id;
+      ret.id = ret.id || ret._id;
+      dataDialog.network._id = dataDialog.network.id = ret.id;
     }
     dataDialog.networks.push(dataDialog.network);
+    dataDialog.networkList.push(dataDialog.network);
   } else if (dataDialog.editNetwork === 2) {
     if (dataDialog.save) {
       const ret: any = await axios.post(
@@ -1306,94 +1348,130 @@ const onOkNetwork = async () => {
     }
   }
 
+  meta2d.store.data.networks = dataDialog.networks;
+  meta2d.connectNetwork();
+  setDot(true);
+
   dataDialog.editNetwork = false;
+  delete dataDialog.networkBak;
 };
 
 const datasetColumns = ref([
   {
     colKey: 'label',
-    title: '名称(数据ID)',
+    title: '数据点名称',
     ellipsis: true,
   },
   {
-    colKey: 'type',
-    title: '类型',
+    colKey: 'id',
+    title: '数据点ID',
     ellipsis: true,
   },
   {
-    colKey: 'value',
-    title: '',
+    colKey: 'type',
+    title: '类型',
     ellipsis: true,
   },
-  {
-    colKey: 'actions',
-    title: '操作',
-    width: 80,
-  },
 ]);
 
-const importDataset = async () => {
-  let columns: any = [
-    {
-      header: '名称',
-      key: 'label',
-    },
-    {
-      header: '数据ID',
-      key: 'key',
-    },
-    {
-      header: '类型',
-      key: 'type',
-    },
-    {
-      header: '场景',
-      key: 'case',
-    },
-    {
-      header: '值',
-      key: 'value',
-    },
-  ];
-  const data: any = await importExcel(columns);
-  dataDialog.dataset = data;
+const addDataset = () => {
+  dataDialog.dataset = {
+    name: '',
+    type: 'dataset',
+    mode: 'api',
+    url: '',
+    data: [],
+  };
+  dataDialog.editDataset = 1;
 };
 
-const onOkDatasources = () => {
-  meta2d.store.data.networks = dataDialog.networks;
-  // @ts-ignore
-  meta2d.store.data.dataset = dataDialog.dataset;
-  // @ts-ignore
-  meta2d.store.data.datasetUrl = dataDialog.datasetUrl;
-  dataDialog.show = false;
+const editDataset = () => {
+  dataDialog.datasetBak = dataDialog.dataset;
+  dataDialog.dataset = JSON.parse(JSON.stringify(dataDialog.dataset));
+  dataDialog.editDataset = 2;
 };
 
-const showAddData = (row?: any) => {
-  if (row) {
-    addDataDialog.header = '编辑数据';
-    addDataDialog.data = row;
+const onOkDataset = async (saveas = false) => {
+  if (!dataDialog.dataset.name) {
+    MessagePlugin.error('名称不能为空');
+    return;
+  }
+
+  const dataset = JSON.parse(JSON.stringify(dataDialog.dataset));
+
+  // 保存到我的数据源
+  if (saveas || dataDialog.editDataset === 1) {
+    delete dataset.id;
+    delete dataset._id;
+    const ret: any = await axios.post(`/api/data/datasources/add`, dataset);
+    if (!ret) {
+      return;
+    }
+    ret.id = ret.id || ret._id;
+    dataDialog.datasetId = ret.id;
+    dataDialog.dataset._id = dataDialog.dataset.id = ret.id;
+    dataDialog.datasetList.push(dataDialog.dataset);
   } else {
-    addDataDialog.header = '添加数据';
-    addDataDialog.data = { type: 'string' };
+    const ret: any = await axios.post(`/api/data/datasources/update`, dataset);
+    if (!ret) {
+      return;
+    }
   }
 
-  addDataDialog.show = true;
+  delete dataset.data;
+  // @ts-ignore
+  meta2d.store.data.dataset = dataset;
+
+  setDot(true);
+
+  dataDialog.editDataset = false;
+  delete dataDialog.datesetBak;
 };
 
-const onOkAddData = () => {
-  if (!addDataDialog.data.label) {
-    MessagePlugin.error('请填写名称');
-    return;
-  }
-  if (!addDataDialog.data.key) {
-    MessagePlugin.error('请填写数据ID');
-    return;
+const onDelDataset = async (item: any, i: number) => {
+  const ret: any = await axios.post(`/api/data/datasources/delete`, {
+    id: item.id,
+  });
+  if (ret) {
+    dataDialog.datasetList.splice(i, 1);
   }
-  if (!dataDialog.dataset) {
-    dataDialog.dataset = [];
+};
+
+const onSelDataset = async (init = false) => {
+  if (dataDialog.datasetId) {
+    const dataset = dataDialog.datasetList.find((item: any) => {
+      return item.id === dataDialog.datasetId;
+    });
+
+    if (!dataset) {
+      return;
+    }
+
+    if (dataset.url) {
+      const ret = await axios.get(dataset.url);
+      if (ret) {
+        dataset.data = ret;
+      }
+    } else {
+      const ret = await axios.post(`/api/data/datasources/get`, {
+        id: dataset.id,
+      });
+      if (ret?.data) {
+        dataset.data = ret.data;
+      }
+    }
+
+    dataDialog.dataset = dataset;
+
+    if (!init) {
+      const d = JSON.parse(JSON.stringify(dataset));
+      delete d.data;
+      // @ts-ignore
+      meta2d.store.data.dataset = d;
+
+      setDot(true);
+    }
   }
-  dataDialog.dataset.push(addDataDialog.data);
-  addDataDialog.show = false;
 };
 
 const share = async () => {
@@ -1551,6 +1629,7 @@ const onSuccessChargeCloud = () => {
     height: 40px;
     flex-shrink: 0;
     padding: 0 12px;
+    z-index: 2;
 
     a {
       display: flex;
@@ -1578,6 +1657,7 @@ const onSuccessChargeCloud = () => {
   #meta2d {
     border-top: 1px solid var(--color-background-input);
     height: calc(100vh - 81px);
+    z-index: 1;
 
     :deep(.meta2d-map) {
       background: var(--color-background);

+ 3 - 3
vite.config.ts

@@ -29,9 +29,9 @@ export default defineConfig({
   },
   server: {
     proxy: {
-      '/image': 'https://2d.le5le.com/',
-      '/file': 'https://2d.le5le.com/',
-      '/api': 'https://2d.le5le.com/',
+      '/image': 'https://v.le5le.com/',
+      '/file': 'https://v.le5le.com/',
+      '/api': 'https://v.le5le.com/',
     },
   },
 });