Quellcode durchsuchen

fix(robot): 对齐机器人配置表单校验与提交规范化

前端必填/选填、数值范围与 min≤max 校验与后端 validate 一致,提交前规范化 symbol 与可空字段。

Co-authored-by: Cursor <cursoragent@cursor.com>
jean vor 1 Monat
Ursprung
Commit
df086dc43c
2 geänderte Dateien mit 330 neuen und 21 gelöschten Zeilen
  1. 196 6
      pnpm-lock.yaml
  2. 134 15
      src/views/coinSpot/robotConfig/index.vue

+ 196 - 6
pnpm-lock.yaml

@@ -74,6 +74,9 @@ importers:
       socket.io-client:
         specifier: ^4.6.1
         version: 4.8.3
+      tronweb:
+        specifier: ^6.3.0
+        version: 6.3.0
       vue:
         specifier: ^3.2.37
         version: 3.5.29(typescript@4.9.5)
@@ -135,6 +138,9 @@ importers:
 
 packages:
 
+  '@adraffy/ens-normalize@1.10.1':
+    resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==}
+
   '@babel/code-frame@7.29.0':
     resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
     engines: {node: '>=6.9.0'}
@@ -152,6 +158,10 @@ packages:
     engines: {node: '>=6.0.0'}
     hasBin: true
 
+  '@babel/runtime@7.26.10':
+    resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==}
+    engines: {node: '>=6.9.0'}
+
   '@babel/runtime@7.28.6':
     resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
     engines: {node: '>=6.9.0'}
@@ -331,6 +341,20 @@ packages:
   '@jridgewell/sourcemap-codec@1.5.5':
     resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
 
+  '@noble/curves@1.2.0':
+    resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==}
+
+  '@noble/curves@1.4.2':
+    resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==}
+
+  '@noble/hashes@1.3.2':
+    resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==}
+    engines: {node: '>= 16'}
+
+  '@noble/hashes@1.4.0':
+    resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
+    engines: {node: '>= 16'}
+
   '@nodelib/fs.scandir@2.1.5':
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
     engines: {node: '>= 8'}
@@ -380,42 +404,36 @@ packages:
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
-    libc: [glibc]
 
   '@parcel/watcher-linux-arm-musl@2.5.6':
     resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
-    libc: [musl]
 
   '@parcel/watcher-linux-arm64-glibc@2.5.6':
     resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   '@parcel/watcher-linux-arm64-musl@2.5.6':
     resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   '@parcel/watcher-linux-x64-glibc@2.5.6':
     resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@parcel/watcher-linux-x64-musl@2.5.6':
     resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@parcel/watcher-win32-arm64@2.5.6':
     resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
@@ -439,6 +457,15 @@ packages:
     resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
     engines: {node: '>= 10.0.0'}
 
+  '@scure/base@1.1.9':
+    resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==}
+
+  '@scure/bip32@1.4.0':
+    resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==}
+
+  '@scure/bip39@1.3.0':
+    resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==}
+
   '@socket.io/component-emitter@3.1.2':
     resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
 
@@ -474,6 +501,9 @@ packages:
   '@types/node@18.19.130':
     resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==}
 
+  '@types/node@22.7.5':
+    resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==}
+
   '@types/normalize-package-data@2.4.4':
     resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
 
@@ -702,6 +732,9 @@ packages:
   aes-js@3.0.0:
     resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==}
 
+  aes-js@4.0.0-beta.5:
+    resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==}
+
   agent-base@6.0.2:
     resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
     engines: {node: '>= 6.0.0'}
@@ -816,6 +849,9 @@ packages:
   axios@0.27.2:
     resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
 
+  axios@1.15.0:
+    resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
+
   balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
 
@@ -835,6 +871,9 @@ packages:
   big.js@5.2.2:
     resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
 
+  bignumber.js@9.1.2:
+    resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==}
+
   bluebird@3.7.2:
     resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
 
@@ -1385,12 +1424,22 @@ packages:
     resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
     engines: {node: '>= 0.6'}
 
+  ethereum-cryptography@2.2.1:
+    resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==}
+
   ethers@5.7.2:
     resolution: {integrity: sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==}
 
+  ethers@6.13.5:
+    resolution: {integrity: sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ==}
+    engines: {node: '>=14.0.0'}
+
   event-emitter@0.3.5:
     resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
 
+  eventemitter3@5.0.1:
+    resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
+
   exit-on-epipe@1.0.1:
     resolution: {integrity: sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==}
     engines: {node: '>=0.8'}
@@ -1579,6 +1628,9 @@ packages:
     resolution: {integrity: sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==}
     engines: {node: '>= 0.10'}
 
+  google-protobuf@3.21.4:
+    resolution: {integrity: sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==}
+
   gopd@1.2.0:
     resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
     engines: {node: '>= 0.4'}
@@ -2441,6 +2493,10 @@ packages:
     resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
     engines: {node: '>=10'}
 
+  proxy-from-env@2.1.0:
+    resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
+    engines: {node: '>=10'}
+
   prr@1.0.1:
     resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
 
@@ -2502,6 +2558,9 @@ packages:
     resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
     engines: {node: '>= 0.4'}
 
+  regenerator-runtime@0.14.1:
+    resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+
   regex-not@1.0.2:
     resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==}
     engines: {node: '>=0.10.0'}
@@ -2615,6 +2674,11 @@ packages:
     resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
     hasBin: true
 
+  semver@7.7.1:
+    resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
+    engines: {node: '>=10'}
+    hasBin: true
+
   semver@7.7.4:
     resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
     engines: {node: '>=10'}
@@ -2879,12 +2943,18 @@ packages:
     resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
     engines: {node: '>=8'}
 
+  tronweb@6.3.0:
+    resolution: {integrity: sha512-5CAjDO4/KfymgjKFgnXgfKKQp0xgOn8otCBYjgYAEIpLZDKNAk14Z0dDeg0UqYuceCiyMHjW7a19Rsz8EmhAOw==}
+
   true-case-path@1.0.3:
     resolution: {integrity: sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==}
 
   tslib@2.3.0:
     resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
 
+  tslib@2.7.0:
+    resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
+
   tunnel-agent@0.6.0:
     resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
 
@@ -2938,6 +3008,9 @@ packages:
   undici-types@5.26.5:
     resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
 
+  undici-types@6.19.8:
+    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+
   union-value@1.0.1:
     resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==}
     engines: {node: '>=0.10.0'}
@@ -2978,6 +3051,10 @@ packages:
   validate-npm-package-license@3.0.4:
     resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
 
+  validator@13.15.23:
+    resolution: {integrity: sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==}
+    engines: {node: '>= 0.10'}
+
   vary@1.1.2:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
@@ -3112,6 +3189,18 @@ packages:
       utf-8-validate:
         optional: true
 
+  ws@8.17.1:
+    resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: '>=5.0.2'
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
   ws@8.18.3:
     resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
     engines: {node: '>=10.0.0'}
@@ -3168,6 +3257,8 @@ packages:
 
 snapshots:
 
+  '@adraffy/ens-normalize@1.10.1': {}
+
   '@babel/code-frame@7.29.0':
     dependencies:
       '@babel/helper-validator-identifier': 7.28.5
@@ -3182,6 +3273,10 @@ snapshots:
     dependencies:
       '@babel/types': 7.29.0
 
+  '@babel/runtime@7.26.10':
+    dependencies:
+      regenerator-runtime: 0.14.1
+
   '@babel/runtime@7.28.6': {}
 
   '@babel/types@7.29.0':
@@ -3529,6 +3624,18 @@ snapshots:
 
   '@jridgewell/sourcemap-codec@1.5.5': {}
 
+  '@noble/curves@1.2.0':
+    dependencies:
+      '@noble/hashes': 1.3.2
+
+  '@noble/curves@1.4.2':
+    dependencies:
+      '@noble/hashes': 1.4.0
+
+  '@noble/hashes@1.3.2': {}
+
+  '@noble/hashes@1.4.0': {}
+
   '@nodelib/fs.scandir@2.1.5':
     dependencies:
       '@nodelib/fs.stat': 2.0.5
@@ -3612,6 +3719,19 @@ snapshots:
       '@parcel/watcher-win32-x64': 2.5.6
     optional: true
 
+  '@scure/base@1.1.9': {}
+
+  '@scure/bip32@1.4.0':
+    dependencies:
+      '@noble/curves': 1.4.2
+      '@noble/hashes': 1.4.0
+      '@scure/base': 1.1.9
+
+  '@scure/bip39@1.3.0':
+    dependencies:
+      '@noble/hashes': 1.4.0
+      '@scure/base': 1.1.9
+
   '@socket.io/component-emitter@3.1.2': {}
 
   '@sxzz/popperjs-es@2.11.8': {}
@@ -3638,6 +3758,10 @@ snapshots:
     dependencies:
       undici-types: 5.26.5
 
+  '@types/node@22.7.5':
+    dependencies:
+      undici-types: 6.19.8
+
   '@types/normalize-package-data@2.4.4': {}
 
   '@types/nprogress@0.2.3': {}
@@ -3943,6 +4067,8 @@ snapshots:
 
   aes-js@3.0.0: {}
 
+  aes-js@4.0.0-beta.5: {}
+
   agent-base@6.0.2:
     dependencies:
       debug: 4.4.3
@@ -4045,6 +4171,14 @@ snapshots:
     transitivePeerDependencies:
       - debug
 
+  axios@1.15.0:
+    dependencies:
+      follow-redirects: 1.15.11
+      form-data: 4.0.5
+      proxy-from-env: 2.1.0
+    transitivePeerDependencies:
+      - debug
+
   balanced-match@1.0.2: {}
 
   base@0.11.2:
@@ -4082,6 +4216,8 @@ snapshots:
 
   big.js@5.2.2: {}
 
+  bignumber.js@9.1.2: {}
+
   bluebird@3.7.2: {}
 
   bn.js@4.12.3: {}
@@ -4715,6 +4851,13 @@ snapshots:
 
   etag@1.8.1: {}
 
+  ethereum-cryptography@2.2.1:
+    dependencies:
+      '@noble/curves': 1.4.2
+      '@noble/hashes': 1.4.0
+      '@scure/bip32': 1.4.0
+      '@scure/bip39': 1.3.0
+
   ethers@5.7.2:
     dependencies:
       '@ethersproject/abi': 5.7.0
@@ -4751,11 +4894,26 @@ snapshots:
       - bufferutil
       - utf-8-validate
 
+  ethers@6.13.5:
+    dependencies:
+      '@adraffy/ens-normalize': 1.10.1
+      '@noble/curves': 1.2.0
+      '@noble/hashes': 1.3.2
+      '@types/node': 22.7.5
+      aes-js: 4.0.0-beta.5
+      tslib: 2.7.0
+      ws: 8.17.1
+    transitivePeerDependencies:
+      - bufferutil
+      - utf-8-validate
+
   event-emitter@0.3.5:
     dependencies:
       d: 1.0.2
       es5-ext: 0.10.64
 
+  eventemitter3@5.0.1: {}
+
   exit-on-epipe@1.0.1: {}
 
   expand-brackets@2.1.4:
@@ -4990,6 +5148,8 @@ snapshots:
       lodash: 4.17.23
       minimatch: 3.0.8
 
+  google-protobuf@3.21.4: {}
+
   gopd@1.2.0: {}
 
   graceful-fs@4.2.11: {}
@@ -5871,6 +6031,8 @@ snapshots:
       err-code: 2.0.3
       retry: 0.12.0
 
+  proxy-from-env@2.1.0: {}
+
   prr@1.0.1:
     optional: true
 
@@ -5948,6 +6110,8 @@ snapshots:
       get-proto: 1.0.1
       which-builtin-type: 1.2.1
 
+  regenerator-runtime@0.14.1: {}
+
   regex-not@1.0.2:
     dependencies:
       extend-shallow: 3.0.2
@@ -6079,6 +6243,8 @@ snapshots:
 
   semver@5.7.2: {}
 
+  semver@7.7.1: {}
+
   semver@7.7.4: {}
 
   set-blocking@2.0.0: {}
@@ -6426,12 +6592,30 @@ snapshots:
 
   trim-newlines@3.0.1: {}
 
+  tronweb@6.3.0:
+    dependencies:
+      '@babel/runtime': 7.26.10
+      axios: 1.15.0
+      bignumber.js: 9.1.2
+      ethereum-cryptography: 2.2.1
+      ethers: 6.13.5
+      eventemitter3: 5.0.1
+      google-protobuf: 3.21.4
+      semver: 7.7.1
+      validator: 13.15.23
+    transitivePeerDependencies:
+      - bufferutil
+      - debug
+      - utf-8-validate
+
   true-case-path@1.0.3:
     dependencies:
       glob: 7.2.3
 
   tslib@2.3.0: {}
 
+  tslib@2.7.0: {}
+
   tunnel-agent@0.6.0:
     dependencies:
       safe-buffer: 5.2.1
@@ -6501,6 +6685,8 @@ snapshots:
 
   undici-types@5.26.5: {}
 
+  undici-types@6.19.8: {}
+
   union-value@1.0.1:
     dependencies:
       arr-union: 3.1.0
@@ -6540,6 +6726,8 @@ snapshots:
       spdx-correct: 3.2.0
       spdx-expression-parse: 3.0.1
 
+  validator@13.15.23: {}
+
   vary@1.1.2: {}
 
   verror@1.10.0:
@@ -6677,6 +6865,8 @@ snapshots:
 
   ws@7.4.6: {}
 
+  ws@8.17.1: {}
+
   ws@8.18.3: {}
 
   xlsx@0.16.9:

+ 134 - 15
src/views/coinSpot/robotConfig/index.vue

@@ -145,7 +145,14 @@ async function handleSubmit() {
   if (!valid) return
   submitting.value = true
   try {
-    const payload = { ...form }
+    const payload: ConfigRobot = {
+      ...form,
+      symbol: form.symbol.trim().toLowerCase(),
+      targetPrice: normalizeOptionalDecimal(form.targetPrice),
+      upProbability: normalizeOptionalDecimal(form.upProbability),
+      minuteChangeLimit: normalizeOptionalDecimal(form.minuteChangeLimit),
+      remark: form.remark?.trim() || undefined,
+    }
     if (isEdit.value) {
       await configRobotUpdate(payload)
       ElMessage.success('修改成功')
@@ -202,21 +209,133 @@ function statusLabel(val: number) {
   return item?.label ?? String(val)
 }
 
+function parsePositiveNum(val: unknown): number | null {
+  if (val === '' || val === null || val === undefined) {
+    return null
+  }
+  const n = Number(val)
+  return Number.isFinite(n) ? n : null
+}
+
+function validatePositive(_rule: unknown, value: unknown, callback: (e?: Error) => void) {
+  const n = parsePositiveNum(value)
+  if (n === null || n <= 0) {
+    callback(new Error('必须大于 0'))
+    return
+  }
+  callback()
+}
+
+function validateMinMaxPair(minField: keyof ConfigRobot, maxField: keyof ConfigRobot, label: string) {
+  return (_rule: unknown, _value: unknown, callback: (e?: Error) => void) => {
+    const min = parsePositiveNum(form[minField])
+    const max = parsePositiveNum(form[maxField])
+    if (min !== null && max !== null && min > max) {
+      callback(new Error(`${label}最小值不能大于最大值`))
+      return
+    }
+    callback()
+  }
+}
+
+function validateOptionalProbability(_rule: unknown, value: unknown, callback: (e?: Error) => void) {
+  if (value === '' || value === null || value === undefined) {
+    callback()
+    return
+  }
+  const n = Number(value)
+  if (!Number.isFinite(n) || n < 0 || n > 1) {
+    callback(new Error('必须在 0~1 之间'))
+    return
+  }
+  callback()
+}
+
+function validateOptionalPositive(_rule: unknown, value: unknown, callback: (e?: Error) => void) {
+  if (value === '' || value === null || value === undefined) {
+    callback()
+    return
+  }
+  const n = Number(value)
+  if (!Number.isFinite(n) || n <= 0) {
+    callback(new Error('必须大于 0'))
+    return
+  }
+  callback()
+}
+
+function normalizeOptionalDecimal(val: unknown): string | undefined {
+  if (val === '' || val === null || val === undefined) {
+    return undefined
+  }
+  return String(val)
+}
+
 const rules = {
   symbol: [{ required: true, message: '请选择交易对', trigger: 'change' }],
   trend: [{ required: true, message: '请选择趋势方向', trigger: 'change' }],
-  openPrice: [{ required: true, message: '请输入起始参考价', trigger: 'blur' }],
-  hoverRange: [{ required: true, message: '请输入盘整区间比例', trigger: 'blur' }],
-  minChangeRate: [{ required: true, message: '请输入最小波动率', trigger: 'blur' }],
-  maxChangeRate: [{ required: true, message: '请输入最大波动率', trigger: 'blur' }],
-  hoverMinChangeRate: [{ required: true, message: '请输入盘整最小波动率', trigger: 'blur' }],
-  hoverMaxChangeRate: [{ required: true, message: '请输入盘整最大波动率', trigger: 'blur' }],
-  bridgeMinRatio: [{ required: true, message: '请输入成桥最小比例', trigger: 'blur' }],
-  bridgeMaxRatio: [{ required: true, message: '请输入成桥最大比例', trigger: 'blur' }],
-  klineMaxHighRatio: [{ required: true, message: '请输入K线最高价上限比例', trigger: 'blur' }],
-  klineMaxLowRatio: [{ required: true, message: '请输入K线最低价下限比例', trigger: 'blur' }],
-  intervalMs: [{ required: true, message: '请输入执行间隔', trigger: 'blur' }],
   status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+  openPrice: [
+    { required: true, message: '请输入起始参考价', trigger: 'blur' },
+    { validator: validatePositive, trigger: 'blur' },
+  ],
+  hoverRange: [
+    { required: true, message: '请输入盘整区间比例', trigger: 'blur' },
+    { validator: validatePositive, trigger: 'blur' },
+  ],
+  minChangeRate: [
+    { required: true, message: '请输入趋势最小波动率', trigger: 'blur' },
+    { validator: validatePositive, trigger: 'blur' },
+    { validator: validateMinMaxPair('minChangeRate', 'maxChangeRate', '趋势波动率'), trigger: 'blur' },
+  ],
+  maxChangeRate: [
+    { required: true, message: '请输入趋势最大波动率', trigger: 'blur' },
+    { validator: validatePositive, trigger: 'blur' },
+    { validator: validateMinMaxPair('minChangeRate', 'maxChangeRate', '趋势波动率'), trigger: 'blur' },
+  ],
+  hoverMinChangeRate: [
+    { required: true, message: '请输入盘整最小波动率', trigger: 'blur' },
+    { validator: validatePositive, trigger: 'blur' },
+    { validator: validateMinMaxPair('hoverMinChangeRate', 'hoverMaxChangeRate', '盘整波动率'), trigger: 'blur' },
+  ],
+  hoverMaxChangeRate: [
+    { required: true, message: '请输入盘整最大波动率', trigger: 'blur' },
+    { validator: validatePositive, trigger: 'blur' },
+    { validator: validateMinMaxPair('hoverMinChangeRate', 'hoverMaxChangeRate', '盘整波动率'), trigger: 'blur' },
+  ],
+  bridgeMinRatio: [
+    { required: true, message: '请输入成桥最小比例', trigger: 'blur' },
+    { validator: validateMinMaxPair('bridgeMinRatio', 'bridgeMaxRatio', '成桥比例'), trigger: 'blur' },
+  ],
+  bridgeMaxRatio: [
+    { required: true, message: '请输入成桥最大比例', trigger: 'blur' },
+    { validator: validateMinMaxPair('bridgeMinRatio', 'bridgeMaxRatio', '成桥比例'), trigger: 'blur' },
+  ],
+  klineMaxHighRatio: [
+    { required: true, message: '请输入K线最高价上限比例', trigger: 'blur' },
+    { validator: validatePositive, trigger: 'blur' },
+  ],
+  klineMaxLowRatio: [
+    { required: true, message: '请输入K线最低价下限比例', trigger: 'blur' },
+    { validator: validatePositive, trigger: 'blur' },
+  ],
+  intervalMs: [
+    { required: true, message: '请输入执行间隔', trigger: 'blur' },
+    {
+      validator: (_r: unknown, v: unknown, cb: (e?: Error) => void) => {
+        const n = Number(v)
+        if (!Number.isFinite(n) || n < 1) {
+          cb(new Error('不能小于 1ms'))
+          return
+        }
+        cb()
+      },
+      trigger: 'blur',
+    },
+  ],
+  targetPrice: [{ validator: validateOptionalPositive, trigger: 'blur' }],
+  upProbability: [{ validator: validateOptionalProbability, trigger: 'blur' }],
+  minuteChangeLimit: [{ validator: validateOptionalPositive, trigger: 'blur' }],
 }
 
 onMounted(() => {
@@ -335,7 +454,7 @@ onMounted(() => {
           <el-input v-model="form.openPrice" placeholder="0.00" />
         </el-form-item>
 
-        <el-form-item label="目标价格">
+        <el-form-item label="目标价格" prop="targetPrice">
           <el-input v-model="form.targetPrice" placeholder="选填,留空则无目标一直运行" />
           <div class="field-tip">留空(NULL)表示无目标,一直趋势运行</div>
         </el-form-item>
@@ -391,14 +510,14 @@ onMounted(() => {
           <div class="field-tip">low ≥ close × (1 − ratio)</div>
         </el-form-item>
 
-        <el-form-item label="分钟涨跌幅限制">
+        <el-form-item label="分钟涨跌幅限制" prop="minuteChangeLimit">
           <el-input v-model="form.minuteChangeLimit" placeholder="选填,如 0.10 = 10%,NULL不限制" />
           <div class="field-tip">超出边界时从边界反向弹回随机步长,不填不限制</div>
         </el-form-item>
 
         <el-divider content-position="left">其他参数</el-divider>
 
-        <el-form-item label="上涨概率">
+        <el-form-item label="上涨概率" prop="upProbability">
           <el-input v-model="form.upProbability" placeholder="选填,如 0.6000" />
           <div class="field-tip">NULL时由趋势自动推导:涨=0.65 跌=0.35 横盘=0.50</div>
         </el-form-item>