10 Commits

Author SHA1 Message Date
peterlu14 6144b60541 [update] update change-note 2022-12-29 10:52:44 +08:00
peterlu14 42f5a37e4d [update] build file system for data analysis 2022-12-29 10:49:19 +08:00
peterlu14 2b4f9bb90c [update] concat data from callback to charts & start button emit 2022-12-06 15:32:22 +08:00
peterlu14 63d0b4d79a [update] add chart api setOption & appendData & markLine & Loading 2022-12-06 14:13:02 +08:00
peterlu14 d7675c0b6a [update] add markLineComponent 2022-12-06 14:11:00 +08:00
peterlu14 1fc202c785 [update] add chart structure 2022-12-06 14:10:18 +08:00
peterlu14 804d95d493 [update] add line chart 2022-12-01 11:52:04 +08:00
peterlu14 8925bbe343 [update] add mqtt method publish & unique client_id 2022-12-01 10:33:19 +08:00
peterlu14 a64f0d93da [update] create new page data analysis 2022-12-01 09:56:15 +08:00
peterlu14 ac3b303b2e [update] import mqtt & echarts 2022-12-01 09:54:55 +08:00
31 changed files with 1544 additions and 3 deletions
+14
View File
@@ -0,0 +1,14 @@
### 2022.12.30
- file system
### 2022.12.06
- e-chart
- mqtt
- new page data-analysis
### 2022.11.30
- setup environment
- vite config add path
- import windicss
- add vue-shim.d.ts file (TS2307)
+288 -3
View File
@@ -1315,6 +1315,30 @@
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
},
"bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"dev": true,
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -1395,6 +1419,16 @@
"update-browserslist-db": "^1.0.0"
}
},
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"dev": true,
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"buffer-equal": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz",
@@ -1555,6 +1589,16 @@
"integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==",
"dev": true
},
"commist": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz",
"integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==",
"dev": true,
"requires": {
"leven": "^2.1.0",
"minimist": "^1.1.0"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2029,6 +2073,31 @@
"readable-stream": "^2.0.2"
}
},
"duplexify": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
"integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==",
"dev": true,
"requires": {
"end-of-stream": "^1.4.1",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1",
"stream-shift": "^1.0.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"earcut": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.3.tgz",
@@ -2040,6 +2109,24 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"echarts": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.0.tgz",
"integrity": "sha512-uPsO9VRUIKAdFOoH3B0aNg7NRVdN7aM39/OjovjO9MwmWsAkfGyeXJhK+dbRi51iDrQWliXV60/XwLA7kg3z0w==",
"dev": true,
"requires": {
"tslib": "2.3.0",
"zrender": "5.4.0"
},
"dependencies": {
"tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"dev": true
}
}
},
"electron-to-chromium": {
"version": "1.4.167",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.167.tgz",
@@ -2052,6 +2139,15 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dev": true,
"requires": {
"once": "^1.4.0"
}
},
"epic-spinners": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/epic-spinners/-/epic-spinners-2.0.0.tgz",
@@ -2685,9 +2781,9 @@
"dev": true
},
"follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
},
"form-data": {
"version": "4.0.0",
@@ -2855,6 +2951,29 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
"help-me": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz",
"integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==",
"dev": true,
"requires": {
"glob": "^7.1.6",
"readable-stream": "^3.6.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"human-signals": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz",
@@ -3027,6 +3146,12 @@
"integrity": "sha512-L3BJStEf5NAqNuzrpfbN71dp43mYIcBUlCRea/vdyv5dW/AYa1d4bpelko4SHdY3I6eN9Wzyasxirj1/vv5kmg==",
"dev": true
},
"js-sdsl": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz",
"integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==",
"dev": true
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -3132,6 +3257,12 @@
}
}
},
"leven": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
"integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==",
"dev": true
},
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -3428,6 +3559,67 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"mqtt": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.3.7.tgz",
"integrity": "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw==",
"dev": true,
"requires": {
"commist": "^1.0.0",
"concat-stream": "^2.0.0",
"debug": "^4.1.1",
"duplexify": "^4.1.1",
"help-me": "^3.0.0",
"inherits": "^2.0.3",
"lru-cache": "^6.0.0",
"minimist": "^1.2.5",
"mqtt-packet": "^6.8.0",
"number-allocator": "^1.0.9",
"pump": "^3.0.0",
"readable-stream": "^3.6.0",
"reinterval": "^1.1.0",
"rfdc": "^1.3.0",
"split2": "^3.1.0",
"ws": "^7.5.5",
"xtend": "^4.0.2"
},
"dependencies": {
"concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.0.2",
"typedarray": "^0.0.6"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"mqtt-packet": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.10.0.tgz",
"integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==",
"dev": true,
"requires": {
"bl": "^4.0.2",
"debug": "^4.1.1",
"process-nextick-args": "^2.0.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -3504,6 +3696,16 @@
"boolbase": "^1.0.0"
}
},
"number-allocator": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.12.tgz",
"integrity": "sha512-sGB0qoQGmKimery9JubBQ9pQUr1V/LixJAk3Ygp7obZf6mpSXime8d7XHEobbIimkdZpgjkNlLt6G7LPEWFYWg==",
"dev": true,
"requires": {
"debug": "^4.3.1",
"js-sdsl": "4.1.4"
}
},
"object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
@@ -3721,6 +3923,16 @@
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -3796,6 +4008,18 @@
"resolved": "https://registry.npmjs.org/regression/-/regression-2.0.1.tgz",
"integrity": "sha512-A4XYsc37dsBaNOgEjkJKzfJlE394IMmUPlI/p3TTI9u3T+2a+eox5Pr/CPUqF0eszeWZJPAc6QkroAhuUpWDJQ=="
},
"reinterval": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
"integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==",
"dev": true
},
"resize-detector": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/resize-detector/-/resize-detector-0.3.0.tgz",
"integrity": "sha512-R/tCuvuOHQ8o2boRP6vgx8hXCCy87H1eY9V5imBYeVNyNVpuL9ciReSccLj2gDcax9+2weXy3bc8Vv+NRXeEvQ==",
"dev": true
},
"resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@@ -4026,6 +4250,28 @@
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
},
"split2": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
"integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
"dev": true,
"requires": {
"readable-stream": "^3.0.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
@@ -4073,6 +4319,12 @@
}
}
},
"stream-shift": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
"dev": true
},
"string-argv": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
@@ -4352,6 +4604,16 @@
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.4.tgz",
"integrity": "sha512-KP4lq9uSz0KZbaqCllRhnxMV3mYRsRWJfdsAhZyt5bV5O1RTpoeDptBRV9NOa/JgOpfaA9ane88VF7OjWNK/DA=="
},
"vue-echarts": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/vue-echarts/-/vue-echarts-6.2.3.tgz",
"integrity": "sha512-xHzUvgsgk/asJTcNa8iVVwoovZU3iEUHvmBa3bzbiP3Y6OMxM1YXsoWOKVmVVaUusGs4ob4pSwjwNy2FemAz9w==",
"dev": true,
"requires": {
"resize-detector": "^0.3.0",
"vue-demi": "^0.13.2"
}
},
"vue-eslint-parser": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",
@@ -4512,6 +4774,12 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"dev": true
},
"xlsx": {
"version": "0.18.5",
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
@@ -4575,6 +4843,23 @@
"dev": true
}
}
},
"zrender": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.0.tgz",
"integrity": "sha512-rOS09Z2HSVGFs2dn/TuYk5BlCaZcVe8UDLLjj1ySYF828LATKKdxuakSZMvrDz54yiKPDYVfjdKqcX8Jky3BIA==",
"dev": true,
"requires": {
"tslib": "2.3.0"
},
"dependencies": {
"tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"dev": true
}
}
}
}
}
+3
View File
@@ -52,16 +52,19 @@
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"autoprefixer": "^10.4.4",
"echarts": "^5.4.0",
"eslint": "^8.13.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.7.0",
"husky": "^8.0.1",
"lint-staged": "^13.0.1",
"mqtt": "^4.3.7",
"postcss": "^8.4.12",
"prettier": "^2.6.2",
"typescript": "^4.5.4",
"vite": "^2.9.9",
"vite-plugin-windicss": "^1.8.8",
"vue-echarts": "^6.2.3",
"vue-eslint-parser": "^8.3.0",
"vue-tsc": "^1.0.10",
"windicss": "^3.5.6"
@@ -0,0 +1,62 @@
<template>
<div>
<va-card class="h-full">
<va-card-content class="h-full w-full">
<EChart ref="chart_ref" :option="option" :autoresize="true" />
</va-card-content>
</va-card>
</div>
</template>
<script lang="ts">
import { ref, onMounted } from 'vue'
export default {
setup() {
const option = ref({
title: {
text: 'Traffic Sources',
left: 'center',
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)',
},
legend: {
orient: 'vertical',
left: 'left',
data: ['Direct', 'Email', 'Ad Networks', 'Video Ads', 'Search Engines'],
},
series: [
{
name: 'Traffic Sources',
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [
{ value: 335, name: 'Direct' },
{ value: 310, name: 'Email' },
{ value: 234, name: 'Ad Networks' },
{ value: 135, name: 'Video Ads' },
{ value: 1548, name: 'Search Engines' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
})
const chart_ref = ref<HTMLElement | null>(null)
onMounted(() => {
console.log(chart_ref.value)
})
return { option, chart_ref }
},
}
</script>
<style lang=""></style>
@@ -0,0 +1,50 @@
<template>
<div>
<va-card class="h-full">
<va-card-content>
<va-select
v-model="simpleSelectModel"
:label="'Filter'"
text-by="description"
track-by="id"
:options="simpleOptions"
/>
<va-button class="mt-2" @click="filterData">Start</va-button>
<!-- <div v-if="simpleSelectModel.id === 1">
<va-button>test</va-button>
<va-input></va-input>
</div> -->
</va-card-content>
</va-card>
</div>
</template>
<script lang="ts">
import { ref } from 'vue'
export default {
emits: ['filter-data'],
setup(props, { emit }) {
const simpleSelectModel = ref({})
const simpleOptions = ref([
{
id: 1,
description: 'First option',
},
{
id: 2,
description: 'Second option',
},
{
id: 3,
description: 'Third option',
},
])
const filterData = function () {
emit('filter-data')
}
return { props, simpleSelectModel, simpleOptions, filterData }
},
}
</script>
<style lang=""></style>
@@ -0,0 +1,23 @@
<template>
<va-card class="">
<div class="h-200px">
<va-button @click="showModal">FILE</va-button>
<Suspense>
<FileModal ref="file_modal_ref"></FileModal>
</Suspense>
</div>
</va-card>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import FileModal from '@/components/data-analysis/FileModal.vue'
// file modal control
let file_modal_ref = ref<InstanceType<typeof FileModal> | null>(null)
const showModal = function () {
file_modal_ref.value?.showModal()
}
defineExpose({ file_modal_ref, showModal })
</script>
+108
View File
@@ -0,0 +1,108 @@
<template>
<div>
<va-modal v-model="show" :fixed-layout="true" size="large">
<!-- File Container-->
<div class="h-[75vh] w-[50vw]">
<!-- Control Bar -->
<div class="d-flex align-center">
<va-input v-model="filter" placeholder="Filter..." class="mr-3" style="flex: 0 200px" clearable />
<!-- <va-checkbox v-model="isFilterCaseSensitive" label="Case sensitive" /> -->
<va-button-group class="mr-3">
<va-button @click="changeFileView('time')">Time</va-button>
<va-button @click="changeFileView('folder')">Folder</va-button>
<va-button @click="changeFileView('project')">Project</va-button>
</va-button-group>
<va-button-group>
<va-button @click="expandAll(true)">Expand</va-button>
<va-button @click="expandAll(false)">Close</va-button>
</va-button-group>
</div>
<!-- File Window-->
<div class="grid grid-cols-4 gap-6">
<!-- File View-->
<div class="h-full col-span-3">
<va-tree-view
v-model:checked="selectedNodes"
v-model:expanded="expanedNodes"
:nodes="nodes"
class="customizable-content"
:filter="filter"
:filter-method="customFilterMethod"
:track-by="'idType'"
:value-by="'idType'"
selectable
>
<template #content="node">
<div class="align-center">
<va-icon v-if="node.type === 'Folder' && node.expanded === false" class="fas fa-folder" />
<va-icon v-if="node.type === 'Folder' && node.expanded === true" class="fas fa-folder-open" />
<va-icon v-if="node.type === 'MetaFile'" class="fas fa-file" />
{{ node.name }}
</div>
</template>
</va-tree-view>
<!-- <p class="mt-4">Expanded nodes: {{ expanedNodes.join(',') || 'none' }}</p> -->
<p class="mt-4">Selected nodes: {{ selectedNodes.join(',') || 'none' }}</p>
</div>
<!-- Channel & Group setting-->
<div class="h-full col-span-1">XY</div>
</div>
</div>
</va-modal>
</div>
</template>
<script setup lang="ts">
import { Ref, ref, computed } from 'vue'
import { FileView, Project, MetaFile, Folder, Time } from '@/utils/file'
const customFilterMethod = computed(() => {
return (node: Project | MetaFile, filterText: string, key: string) => {
// console.log('customFilterMethod', node, filterText, key, node.name.includes(filterText))
return node.name.includes(filterText)
}
})
// handling show modal
let show = ref(false)
const showModal = function () {
show.value = !show.value
}
// handling init file
const fileView = new FileView()
await fileView.appendChildren(1)
let fileViewChildrenForNodes = fileView.getChildren()
// handling modal header
const changeFileView = async function (view: string) {
fileView.changeView(view)
await fileView.appendChildren(1)
fileViewChildrenForNodes = fileView.getChildren()
nodes.value.length = 0
nodes.value.push(...fileViewChildrenForNodes)
expanedNodes.value.length = 0
expanedNodes.value.push(nodes.value[0].idType)
}
const expandAll = function (expandOrNot: boolean) {
expanedNodes.value.length = 0
if (!expandOrNot) return
for (const node of nodes.value) {
expanedNodes.value.push(node.idType)
}
}
// handling tree view
const filter = ref('')
const isFilterCaseSensitive = ref(false)
const selectedNodes = ref([])
const expanedNodes: Ref<(never | string)[]> = ref([])
const nodes: Ref<(never | Project | Folder | Time | MetaFile)[]> = ref([])
nodes.value.push(...fileViewChildrenForNodes)
expanedNodes.value.push(nodes.value[0].idType)
console.log('nodes', nodes)
defineExpose({ show, showModal, nodes, expanedNodes, selectedNodes })
</script>
<style lang="scss"></style>
@@ -0,0 +1,63 @@
<template>
<div>
<va-card class="h-full">
<va-card-content class="h-full w-full">
<EChart ref="chart_ref" :option="option" :autoresize="true" :loading="loading" />
</va-card-content>
</va-card>
</div>
</template>
<script setup lang="ts">
import LineChart from '@/data/echart/composables/LineChart'
import { EChartsCoreOption } from 'echarts'
import { ref } from 'vue'
const loading = ref(false)
const { chart_ref, option } = LineChart()
const setOption = function (option: EChartsCoreOption) {
chart_ref.value?.setOption(option)
}
const appendData = function (seriesIndex: number, data: Array<number | string>) {
chart_ref.value?.appendData({
seriesIndex: seriesIndex,
data: data,
})
chart_ref.value?.resize()
}
const markLine = function (
seriesIndex: number,
markLineIndex: number,
header: string,
value: number | Array<number> | string,
) {
chart_ref.value?.setOption({
series: [
{
markLine: {
data: [{ name: header, yAxis: value }],
},
},
],
})
}
const startLoading = function () {
loading.value = true
}
const stopLoading = function () {
loading.value = false
}
defineExpose({
startLoading,
stopLoading,
setOption,
appendData,
markLine,
})
</script>
<style lang=""></style>
+37
View File
@@ -0,0 +1,37 @@
import MQTT from '@/utils/mqtt'
import type { OnMessageCallback } from 'mqtt'
import { onUnmounted, ref } from 'vue'
export default function useMqtt() {
const PublicMqtt = ref<MQTT | null>(null)
const startMqtt = (val: string, callback: OnMessageCallback) => {
// set mqtt topic
PublicMqtt.value = new MQTT(val)
// init mqtt
PublicMqtt.value.init()
// link mqtt
PublicMqtt.value.link()
getMessage(callback)
}
const publish = (topic: string, message: string) => {
PublicMqtt.value?.publish(topic, message)
}
const getMessage = (callback: OnMessageCallback) => {
// PublicMqtt.value?.client.on('message', callback);
PublicMqtt.value?.get(callback)
}
onUnmounted(() => {
if (PublicMqtt.value) {
PublicMqtt.value.unsubscribes()
PublicMqtt.value.over()
}
})
return {
startMqtt,
publish,
}
}
+13
View File
@@ -0,0 +1,13 @@
import axios, { Axios } from 'axios'
export const setBaseURL = function (baseURL: string) {
if (baseURL) {
axios.defaults.baseURL = `http://${baseURL}:3000`
}
}
setBaseURL(location.href.split('/')[2].split(':')[0])
export const getAxios = function (): Axios {
return axios
}
+20
View File
@@ -0,0 +1,20 @@
import { EChartsOption } from 'echarts'
import { generateLegend } from './legend'
import { generateGrid } from './grid'
import { generateXAxis } from './xAxis'
import { generateYAxis } from './yAxis'
import { generateTooltip } from './tooltip'
import { generateSeries } from './series'
import { generateDataZoom } from './datazoom'
export const generateChart = function (): EChartsOption {
return {
legend: generateLegend(),
tooltip: generateTooltip(),
grid: [generateGrid()],
xAxis: [generateXAxis()],
yAxis: [generateYAxis()],
series: [generateSeries('')],
dataZoom: generateDataZoom(),
}
}
+10
View File
@@ -0,0 +1,10 @@
import { ref } from 'vue'
import { ECharts } from 'echarts'
import { generateChart } from '@/data/echart/chart'
export default function lineChart() {
const chart_ref = ref<ECharts | null>(null)
const option = generateChart()
return { option, chart_ref }
}
+20
View File
@@ -0,0 +1,20 @@
import { DataZoomComponentOption } from 'echarts'
export const generateDataZoom = function (): DataZoomComponentOption | DataZoomComponentOption[] {
return [
{
show: true,
realtime: true,
start: 0,
end: 100,
xAxisIndex: [0, 1],
},
{
type: 'inside',
realtime: true,
start: 0,
end: 100,
xAxisIndex: [0, 1],
},
]
}
+5
View File
@@ -0,0 +1,5 @@
import { TooltipComponentFormatterCallbackParams } from 'echarts'
export const generateFormatter = function (params: TooltipComponentFormatterCallbackParams): string {
return String(params[0].value)
}
+18
View File
@@ -0,0 +1,18 @@
import { GridComponentOption } from 'echarts'
export const generateGrid = function (): GridComponentOption {
const index = 0
const length = 1
// const chartID = 1
const _height = 82 / length - 2 + '%'
const _top = index * (82 / length) + 1 + '%'
const _left = '8%'
const _right = '8%'
return {
top: _top,
height: _height,
left: _left,
right: _right,
}
}
+14
View File
@@ -0,0 +1,14 @@
import { LegendComponentOption } from 'echarts'
export const generateLegend = function (): LegendComponentOption {
return {
type: 'scroll',
data: [],
// x: 'center',
// y: '-10%',
bottom: '0%',
textStyle: {
color: '#34495e',
},
}
}
+19
View File
@@ -0,0 +1,19 @@
import { MarkLineComponentOption } from 'echarts'
export const generateMarkLine = function (): MarkLineComponentOption {
return {
symbol: ['none', 'none'],
// cannot create empty mark line data
data: [
// {
// name: 'ceiling',
// },
// {
// name: 'ground',
// },
// {
// name: 'middle',
// },
],
}
}
+36
View File
@@ -0,0 +1,36 @@
import { LineSeriesOption } from 'echarts'
import { generateMarkLine } from './markLine'
export const generateSeries = function (name: string): LineSeriesOption {
return {
type: 'line',
name: name || '',
showSymbol: false,
// silent: true,
data: [],
// markLine: {
// data: [
// {
// // 固定起点的 x 像素位置,用于模拟一条指向最大值的水平线
// name: 'test',
// yAxis: '200',
// },
// ],
// },
// xAxisIndex: xAxisIndex,
// yAxisIndex: yAxisIndex,
// AxisSource: [deviceID, channel], use it to get data stream from buffer
// xAxisSource: xAxisSource,
// yAxisSource: yAxisSource,
animation: false,
// dataXMax: null,
// dataXMin: null,
// dataYMax: null,
// dataYMin: null,
// itemStyle: {},
// lineStyle: {
// width: 1.5,
// },
markLine: generateMarkLine(),
}
}
+15
View File
@@ -0,0 +1,15 @@
import { TooltipComponentOption } from 'echarts'
import { generateFormatter } from './formatter'
export const generateTooltip = function (): TooltipComponentOption {
return {
padding: 10,
backgroundColor: '#fff',
borderColor: 'rgba(0, 0, 200, 0.2)',
borderWidth: 1,
borderRadius: 0,
trigger: 'axis',
hideDelay: 5000,
formatter: generateFormatter,
}
}
+35
View File
@@ -0,0 +1,35 @@
import { XAXisComponentOption } from 'echarts'
declare const AXIS_TYPES: {
readonly value: 1
readonly category: 1
readonly time: 1
readonly log: 1
}
declare type OptionAxisType = keyof typeof AXIS_TYPES
export const generateXAxis = function (
type?: OptionAxisType,
gridIndex?: number,
// boundaryGap?: [string | number, string | number],
): XAXisComponentOption {
return {
type: type || 'value',
show: true,
gridIndex: gridIndex || 0,
// scale: true,
// boundaryGap: true,
axisLine: { onZero: false, lineStyle: { color: '#aaa' } },
axisTick: { show: false },
axisLabel: {
show: true,
showMinLabel: false,
showMaxLabel: false,
color: '#34495e',
},
splitLine: { show: true, lineStyle: { color: '#aaa' } },
min: 'dataMin',
max: 'dataMax',
// interval: undefined,
}
}
+35
View File
@@ -0,0 +1,35 @@
import { YAXisComponentOption } from 'echarts'
declare const AXIS_TYPES: {
readonly value: 1
readonly category: 1
readonly time: 1
readonly log: 1
}
declare type OptionAxisType = keyof typeof AXIS_TYPES
export const generateYAxis = function (
type?: OptionAxisType,
gridIndex?: number,
// boundaryGap?: [string | number, string | number],
): YAXisComponentOption {
return {
type: type || 'value',
show: true,
gridIndex: gridIndex || 0,
// scale: true,
// boundaryGap: true,
axisLine: { onZero: false, lineStyle: { color: '#aaa' } },
axisTick: { show: false },
axisLabel: {
show: true,
showMinLabel: false,
showMaxLabel: false,
color: '#34495e',
},
splitLine: { show: true, lineStyle: { color: '#aaa' } },
min: 'dataMin',
max: 'dataMax',
// interval: undefined,
}
}
+29
View File
@@ -0,0 +1,29 @@
import { getAxios } from '@/data/axios'
import { AxiosResponse } from 'axios'
const axios = getAxios()
const FOLDER_TYPE = 'folder'
const getCollection = async function (
parent: number,
offset: number,
limit: number,
order: Array<string>,
): Promise<AxiosResponse<any, any>> {
let url = `api/file/collection/get?type=${FOLDER_TYPE}&`
if (parent) url += `parent=${parent}&`
if (offset) url += `offset=${offset}&`
if (limit) url += `limit=${limit}&`
if (order) {
const _order = order?.join('-')
url += `order=${_order}`
}
const result = await axios.get(url)
return result
}
const meta = {
getCollection,
}
export default meta
+11
View File
@@ -0,0 +1,11 @@
import meta from './meta'
import collection from './collection'
import project from './project'
const api = {
collection,
meta,
project,
}
export default api
+51
View File
@@ -0,0 +1,51 @@
import { getAxios } from '@/data/axios'
import { AxiosResponse } from 'axios'
const FOLDER_TYPE = 'folder'
const axios = getAxios()
const getMetaWithTypeFolder = async function (
parent: number,
offset: number,
limit: number,
order: Array<string>,
): Promise<AxiosResponse<any, any>> {
let url = `api/file/meta/get?type=${FOLDER_TYPE}&`
if (parent) url += `parent=${parent}&`
if (offset) url += `offset=${offset}&`
if (limit) url += `limit=${limit}&`
if (order) {
const _order = order?.join('-')
url += `order=${_order}`
}
const result = await axios.get(url)
return result
}
const getMetaByTime = async function (time: string): Promise<AxiosResponse<any, any>> {
const url = `api/file/meta/get?time=${time}`
const result = await axios.get(url)
return result
}
const getMetaGroupByTime = async function (): Promise<AxiosResponse<any, any>> {
const url = `api/file/meta/get?group=time`
const result = await axios.get(url)
return result
}
const getMetaByProject = async function (uuid: string): Promise<AxiosResponse<any, any>> {
const url = `api/file/meta/get?type=project&project=${uuid}`
const result = await axios.get(url)
return result
}
const meta = {
getMetaWithTypeFolder,
getMetaByTime,
getMetaByProject,
getMetaGroupByTime,
}
export default meta
+17
View File
@@ -0,0 +1,17 @@
import { getAxios } from '@/data/axios'
import { AxiosResponse } from 'axios'
const axios = getAxios()
const getProjectGroup = async function (attrs?: Array<string>): Promise<AxiosResponse<any, any>> {
let url = `api/project/report/get?`
if (attrs) url += `attrs=${attrs.join('-')}`
const result = await axios.get(url)
return result
}
const project = {
getProjectGroup,
}
export default project
+38
View File
@@ -9,6 +9,40 @@ import vuesticGlobalConfig from './services/vuestic-ui/global-config'
import App from './App.vue'
import 'virtual:windi.css'
// import ECharts modules
import ECharts from 'vue-echarts'
import { use } from 'echarts/core'
// import ECharts modules manually to reduce bundle size
import { CanvasRenderer } from 'echarts/renderers'
import { LineChart, BarChart, PieChart } from 'echarts/charts'
import {
TitleComponent,
LegendComponent,
GridComponent,
TooltipComponent,
DataZoomComponent,
MarkLineComponent,
ToolboxComponent,
BrushComponent,
} from 'echarts/components'
use([
CanvasRenderer,
LineChart,
PieChart,
BarChart,
GridComponent,
TooltipComponent,
TitleComponent,
LegendComponent,
DataZoomComponent,
ToolboxComponent,
BrushComponent,
MarkLineComponent,
])
import serverApi from '@/data/server-api'
const app = createApp(App)
app.use(stores)
@@ -16,6 +50,10 @@ app.use(router)
app.use(i18n)
app.use(createVuestic({ config: vuesticGlobalConfig }))
app.component('EChart', ECharts)
app.provide('server-api', serverApi)
if (import.meta.env.VITE_APP_GTM_ENABLED) {
app.use(
createGtm({
@@ -0,0 +1,71 @@
<template>
<div class="h-[88vh] grid grid-cols-3 gap-6">
<div class="h-full col-span-1">
<div class="h-1/3">
<FileContainer class="h-9/10"></FileContainer>
</div>
<div class="h-2/3">
<ControlPanel class="h-9/10" @filter-data="filterData"></ControlPanel>
</div>
</div>
<LineChartContainer ref="linechart_ref" class="h-[50%] col-span-2"></LineChartContainer>
</div>
</template>
<script setup lang="ts">
import useMqtt from '@/composables/utils/useMqtt'
import FileContainer from '@/components/data-analysis/FileContainer.vue'
import ControlPanel from '@/components/data-analysis/ControlPanel.vue'
import LineChartContainer from '@/components/data-analysis/LineChartContainer.vue'
import { ref } from 'vue'
// mqtt init
const { startMqtt, publish } = useMqtt()
const controllerID = 'b8:27:eb:18:f8:cc'
// define subscribe and callback
startMqtt(`${controllerID}/data_analysis/#`, (topic: string, message: ArrayBuffer) => {
// [data, data, ceiling, ground]
const msg = JSON.parse(message.toString())
// try draw line
// msg.push(1500000, -1500000)
// reformat data
const _msg = []
for (let i = 0; i < msg[0].length; i++) {
_msg.push([msg[0][i], msg[1][i]])
}
// append Data
linechart_ref.value?.appendData(0, _msg)
// stop loading
linechart_ref.value?.stopLoading()
// draw line
if (msg.length < 3) return true
linechart_ref.value.setOption({
series: [
{
markLine: {
data: [{ yAxis: msg[2] }, { yAxis: msg[3] }],
},
},
],
})
})
// reference
const linechart_ref = ref()
// methods
const filterData = function () {
linechart_ref.value?.startLoading()
publish(`${controllerID}_data_analysis/get_analysis_data`, JSON.stringify({}))
}
defineExpose({ linechart_ref, filterData })
</script>
<style></style>
+5
View File
@@ -22,6 +22,11 @@ const routes: Array<RouteRecordRaw> = [
path: 'dashboard',
component: () => import('../pages/admin/dashboard/Dashboard.vue'),
},
{
name: 'data-analysis',
path: 'data-analysis',
component: () => import('../pages/admin/data-analysis/DataAnalysis.vue'),
},
{
name: 'statistics',
path: 'statistics',
+12
View File
@@ -0,0 +1,12 @@
interface collection {
id: number
name: string
parent: object
controller_id: number
size: number
type: string
description: string
deleted: bool
}
export { collection }
+355
View File
@@ -0,0 +1,355 @@
import api from '@/data/server-api'
declare type FolderType = {
file: number
collection: number
}
class FileView {
children: Array<Project | Folder | Time | MetaFile>
viewBy: string
offset: number
limit: number
constructor() {
this.children = []
this.viewBy = 'time'
this.offset = 0
this.limit = 20
}
init() {
this.children.length = 0
this.offset = 0
this.limit = 20
}
changeView(viewBy: string) {
this.viewBy = viewBy
this.init()
}
async appendChildren(id: number) {
if (this.viewBy === 'folder') {
// append children folder
const folderResult = await this.getCollectionsFromServer(id)
for (const _data of folderResult.rows) {
const folder = new Folder(_data.id, _data.name)
// console.log('folder', folder)
await folder.init()
this.children.push(folder)
}
} else if (this.viewBy == 'time') {
const timeGroup = await this.getTimeGroupFromServer()
for (const time in timeGroup) {
const timeInstance = new Time(time, timeGroup[time]?.date)
await timeInstance.appendChildren()
this.children.push(timeInstance)
}
} else if (this.viewBy == 'project') {
const projectGroup = await this.getProjectGroupFromServer(['id', 'name', 'uuid', 'created_at'])
for (const project of projectGroup) {
const projectInstance = new Project(project.id, project.name, project.uuid, project.created_at)
await projectInstance.appendChildren()
this.children.push(projectInstance)
}
}
// console.log('children', this.children)
}
async getCollectionsFromServer(id: number, order: string[] = ['id', 'desc']) {
const result = await api.collection.getCollection(id, this.offset, this.limit, order)
return result.data
}
async getFilesByIdFromServer(id: number, order: string[] = ['id', 'desc']) {
const result = await api.meta.getMetaWithTypeFolder(id, this.offset, this.limit, order)
return result.data
}
async getTimeGroupFromServer() {
const result = await api.meta.getMetaGroupByTime()
return result.data
}
async getProjectGroupFromServer(attrs: string[]) {
const result = await api.project.getProjectGroup(attrs)
return result.data
}
getChildren() {
return this.children
}
}
class Time {
id: string
type: string
idType: string
name: string
children: Array<Time | MetaFile>
offset: number
limit: number
maxFiles: number
constructor(id: string, date: string) {
this.id = id
this.type = 'Time'
this.idType = 'Time-' + id
this.name = date
this.children = []
this.offset = 0
this.limit = 20
this.maxFiles = 0
}
async getFilesFromServer(id: string) {
const result = await api.meta.getMetaByTime(id)
return result.data
}
async appendChildren() {
const metaFile = await this.getFilesFromServer(this.name)
for (const _metaFile of metaFile) {
const channels = _metaFile.channels === null ? _metaFile.channels : JSON.parse(_metaFile.channels)
const meta = new MetaFile(
_metaFile.id,
_metaFile.uuid,
_metaFile.name,
channels,
_metaFile.device,
_metaFile.parameter_set,
_metaFile.parent,
_metaFile.raw_data,
_metaFile.mini_data,
_metaFile.size,
_metaFile.time_duration,
_metaFile.created_at,
)
this.children.push(meta)
console.log('time', meta)
}
}
getChildren() {
return this.children
}
}
class Project {
id: string
type: string
idType: string
name: string
uuid: string
created_at: Date
children: Array<Project | MetaFile>
offset: number
limit: number
maxFiles: number
constructor(id: string, name: string, uuid: string, created_at: Date) {
this.id = id
this.type = 'Project'
this.idType = 'Project-' + id
this.name = name
this.uuid = uuid
this.created_at = created_at
this.children = []
this.offset = 0
this.limit = 20
this.maxFiles = 0
}
async getFilesFromServer(id: string) {
const result = await api.meta.getMetaByProject(id)
return result.data
}
async appendChildren() {
const metaFile = await this.getFilesFromServer(this.uuid)
for (const _metaFile of metaFile) {
const channels = _metaFile.channels === null ? _metaFile.channels : JSON.parse(_metaFile.channels)
const meta = new MetaFile(
_metaFile.id,
_metaFile.uuid,
_metaFile.name,
channels,
_metaFile.device,
_metaFile.parameter_set,
_metaFile.parent,
_metaFile.raw_data,
_metaFile.mini_data,
_metaFile.size,
_metaFile.time_duration,
_metaFile.created_at,
)
this.children.push(meta)
// console.log('project', meta)
}
}
getChildren() {
return this.children
}
}
class Folder {
id: number
idType: string
name: string
children: Array<Folder | MetaFile>
offset: FolderType
limit: FolderType
maxNumber: FolderType
type: string
constructor(id: number, name: string) {
this.id = id
this.type = 'Folder'
this.idType = 'Folder' + id
this.name = name
this.children = []
this.offset = { file: 0, collection: 0 }
this.limit = { file: 20, collection: 20 }
this.maxNumber = { file: 0, collection: 0 }
}
async init() {
await this.appendChildren()
}
async getFilesFromServer(id: number, offset: number, limit: number, order: Array<string> = ['id', 'desc']) {
const result = await api.meta.getMetaWithTypeFolder(id, offset, limit, order)
return result
}
async getCollectionsFromServer(id: number, offset: number, limit: number, order: Array<string> = ['id', 'desc']) {
const result = await api.collection.getCollection(id, offset, limit, order)
return result
}
async appendChildren() {
// append children folder
await this.appendFolder()
// append children file
await this.appendFile()
}
async appendFolder() {
const folder = await this.getCollectionsFromServer(this.id, this.offset.collection, this.limit.collection)
for (const _data of folder.data.rows) {
const folder = new Folder(_data.id, _data.name)
await folder.appendChildren()
this.children.push(folder)
}
this.maxNumber.collection = folder.data.count
this.offset.collection += this.limit.collection
}
async appendFile() {
const metaFile = await this.getFilesFromServer(this.id, this.offset.file, this.limit.file)
for (const _metaFile of metaFile.data.rows) {
const channels = _metaFile.channels === null ? _metaFile.channels : JSON.parse(_metaFile.channels)
const meta = new MetaFile(
_metaFile.id,
_metaFile.uuid,
_metaFile.name,
channels,
_metaFile.device,
_metaFile.parameter_set,
_metaFile.parent,
_metaFile.raw_data,
_metaFile.mini_data,
_metaFile.size,
_metaFile.time_duration,
_metaFile.created_at,
)
this.children.push(meta)
// console.log('folder', meta)
}
this.maxNumber.file = metaFile.data.count
this.offset.file += this.limit.file
}
getChildren() {
return this.children
}
}
class MetaFile {
id: number
idType: string
uuid: string
name: string
channels: Array<number>
device: Device
parameter: any
parent: object
rawData: Array<Array<number>>
miniData: Array<Array<number>>
size: string
time: string
created: Date
type: string
constructor(
id: number,
uuid: string,
name: string,
channels: Array<number>,
parent: object,
device: Device,
parameter: any,
rawData: Array<Array<number>>,
miniData: Array<Array<number>>,
size: string,
time: string,
created: Date,
) {
this.id = id
this.type = 'MetaFile'
this.idType = 'MetaFile-' + id
this.uuid = uuid
this.name = name
this.channels = channels
this.parent = parent
this.device = device
this.parameter = parameter
this.rawData = rawData
this.miniData = miniData
this.size = size
this.time = time
this.created = created
}
getMetaInfo() {
return {
id: this.id,
uuid: this.uuid,
name: this.name,
channels: this.channels,
parent: this.parent,
device: this.device,
parameter: this.parameter,
rawData: this.rawData,
miniData: this.miniData,
size: this.size,
time: this.time,
created: this.created,
}
}
}
interface Device {
id: number
name: string
version: string
macAddress: string
memoryBoard: number
serialNumber: Array<number>
libraryName: string
libraryVersion: string
}
export { FileView, Project, MetaFile, Folder, Time }
+67
View File
@@ -0,0 +1,67 @@
import type { MqttClient, OnMessageCallback } from 'mqtt'
import * as mqtt from 'mqtt/dist/mqtt.min.js'
class MQTT {
url: string
topic: string
client!: MqttClient
constructor(topic: string) {
this.topic = topic
this.url = 'ws://192.168.2.1:8083'
}
// init mqtt
init() {
const options = {
clean: true,
clientId: 'mqttjs_' + Math.random().toString(16).substring(2, 8),
// password: '111',
// username: '1111',
connectTimeout: 4000,
}
this.client = mqtt.connect(this.url, options)
this.client.on('error', (error: any) => {
console.log(error)
})
this.client.on('reconnect', (error: Error) => {
console.log(error)
})
}
// unsubscribes
unsubscribes() {
this.client.unsubscribe(this.topic, (error: Error) => {
if (!error) {
console.log(this.topic, 'cancel subscribe success')
} else {
console.log(this.topic, 'cancel subscribe fail')
}
})
}
// subscribe
link() {
this.client.on('connect', () => {
this.client.subscribe(this.topic, (error: any) => {
if (!error) {
console.log('subscribe success')
} else {
console.log('subscribe fail')
}
})
})
}
// publish
publish(topic: string, message: string) {
this.client.publish(topic, message)
}
// message callback
get(callback: OnMessageCallback) {
this.client.on('message', callback)
}
// close client
over() {
this.client.end()
}
}
export default MQTT