79 Commits

Author SHA1 Message Date
peterlu14 d67d5cba54 - change text style
- change tumor size
- fixed empty data error
- fixed shapes & annotation refresh problem
- fixed only one marker color problem
- change x-axis dtick
2023-08-31 15:25:40 +08:00
peterlu14 bafa2b1b87 -update chart style & text & color 2023-08-30 09:56:53 +08:00
peterlu14 08109f4e2d -update subject page with new UI and function 2023-08-24 16:09:16 +08:00
peterlu14 40e1f82af0 -fix avatar resouce
-add tumor size display
2023-06-14 17:09:41 +08:00
peterlu14 0305407949 - update subject page 2023-06-05 14:07:27 +08:00
peterlu14 a1e1dd0c72 - fix EIS channel select wrong 2023-04-28 10:26:48 +08:00
peterlu14 556fd0697f - fix reset
- update find with range
2023-04-27 11:56:51 +08:00
peterlu14 47f5da9efc - disable zoom in when max min boundry is given
- file info add working mode & date
- filter size 0 data
- format code
2023-03-23 12:53:38 +08:00
peterlu14 9bf9db5263 - add right click context menu 2023-03-20 17:40:34 +08:00
peterlu14 8070da4270 - echarts series sampling with lttb
- echarts add color support
- support line in batch mode
- create canvas class
- add find meta files api
- auto import boundry from interactive mode
2023-03-17 17:09:16 +08:00
peterlu14 e666d75772 - support time channel
- support none filter mode
2023-03-14 10:36:57 +08:00
peterlu14 4507ee1c35 - file loading 2 second
- default channel [1,2]
2023-03-07 17:44:49 +08:00
peterlu14 6537664c04 - let to const 2023-03-07 16:48:30 +08:00
peterlu14 ab2b3b2fa4 -remove import defineExpose & defineEmit 2023-03-07 16:47:22 +08:00
peterlu14 8c6f551a35 -remove useless files 2023-03-07 16:41:43 +08:00
peterlu14 3c835eec6a - upgrade modules in package.json (-ionicons) 2023-03-07 16:30:15 +08:00
peterlu14 c3570d1259 - add canvas class and store
- add data-analysis store to save batchMode
- channel store to save generate x & y axis
- data-analysis page display can be switch to display chart
2023-03-06 17:17:41 +08:00
peterlu14 1230a5ffa6 [update] remove useless sidebar 2023-03-02 15:22:57 +08:00
peterlu14 2fd179b73d -file slider loading
-change message format
-disable console
-add console time
-callback func data format change
-remove time limit
-grid show remove
2023-02-13 09:59:42 +08:00
peterlu14 feeb423e24 - get file data onMounted 2023-02-04 18:01:43 +08:00
peterlu14 a1c202fc5c - add chartAPI to series markLine formatter
- rename XAxis & YAxis class
2023-02-04 18:01:03 +08:00
peterlu14 1a5bcf0a58 - chart reset function to fix series not render problem 2023-02-04 15:57:03 +08:00
peterlu14 6b7e5015e7 - let series & markLine tooltip follow unit & scale 2023-02-04 15:55:33 +08:00
peterlu14 d2ccfc63a4 - chart grid height 80% to 75% 2023-02-03 18:27:12 +08:00
peterlu14 c94d4360e6 -add yAxisManager setting in click start button
-replace x-axis setting with axis-setting can both use by x&y axis
-remove chart watch select files
2023-02-03 18:23:25 +08:00
peterlu14 62f62ef5e5 [update] add y-axis manager 2023-02-03 17:02:39 +08:00
peterlu14 aadbb6c482 [update] legend locate bottom & x-axis default name remove 2023-02-03 16:50:10 +08:00
peterlu14 69dc2a718f [update] x-axis fixed & interval follow the unit 2023-02-03 16:49:11 +08:00
peterlu14 4fc46ca4ea [update] x-axis change unit 2023-02-03 15:04:25 +08:00
peterlu14 840f0af14b [update] chart build timing from select file to press start button 2023-02-03 11:07:58 +08:00
peterlu14 53d131376d [update] add Grid & tooltip 2023-02-03 10:12:54 +08:00
peterlu14 f83f8eb8f8 [update] folder rename 2023-02-03 10:10:05 +08:00
peterlu14 68356fbd04 [update] add xAxisManager & giveup setOption & appendData api 2023-02-02 11:20:07 +08:00
peterlu14 a357102e82 [update] restruct again 2023-02-01 11:19:57 +08:00
peterlu14 6ac8e8cd4a [update] resturct data-analysis files struct 2023-02-01 11:16:06 +08:00
peterlu14 3cbc572592 test 2023-01-31 17:50:28 +08:00
peterlu14 ef8ce2a0c4 [update] add chart series 2023-01-31 10:37:06 +08:00
peterlu14 9000fabdea [update] chart init & series data api init 2023-01-18 17:29:48 +08:00
peterlu14 06afc2e1a3 [update] fix render problem 2023-01-13 11:51:25 +08:00
peterlu14 3dfef28777 [update] filetable update 2023-01-12 17:37:54 +08:00
peterlu14 4d48e82997 [update] add new slideout & table 2023-01-12 16:06:51 +08:00
peterlu14 5af4215562 Merge remote-tracking branch 'origin/10-test' into dev/file_modal 2023-01-06 13:41:44 +08:00
peterlu14 4f94fcf541 [update] temp get all files 2023-01-06 13:40:59 +08:00
peterlu14 22afbab8ff [update] fix vue-router deploy apache problem 2023-01-06 13:40:16 +08:00
lai8928 ef22f1d20b [update]button link 2023-01-06 13:05:52 +08:00
peterlu14 99f401bac0 [update] router default page direct to data-analysis 2023-01-06 11:13:42 +08:00
peterlu14 3d751f746b [debug] temp fix typescript error 2023-01-06 11:12:49 +08:00
peterlu14 2df4ccf13c [update] fix channel 2023-01-06 10:42:20 +08:00
lai8928 0786113589 Merge remote-tracking branch 'origin/dev/file_modal' into 10-test 2023-01-04 10:19:33 +08:00
lai8928 7499f34dc7 change controller id 2023-01-04 10:17:52 +08:00
peterlu14 a65fea33a9 [update] change control panel to script setup & fix style error 2023-01-04 10:15:11 +08:00
peterlu14 90ba4b448a Merge remote-tracking branch 'origin/10-test' into dev/file_modal 2023-01-03 17:33:42 +08:00
lai8928 dfb3a96f84 [update] merge file modal 2023-01-03 16:50:40 +08:00
lai8928 58eecd80ae Merge remote-tracking branch 'origin/dev/file_modal' into 10-test 2022-12-30 15:58:27 +08:00
peterlu14 ec29b2fbc7 [update] show info 2022-12-30 15:20:11 +08:00
peterlu14 b8a7b39b62 [update] pass fileview-select & channel by create pinia store 2022-12-30 15:17:43 +08:00
peterlu14 4e041645a8 [update] channel X & Y select and emit 2022-12-30 09:53:35 +08:00
peterlu14 5fe5a64140 [update] import config table 2022-12-30 09:51:23 +08:00
peterlu14 746f4d98ec [update] restruct Device type & add getMetaFile 2022-12-30 09:48:47 +08:00
lai8928 4e8254e2d7 add csv download test 2022-12-29 14:09:05 +08:00
peterlu14 7043581335 [update] change-note 2022-12-29 11:42:35 +08:00
peterlu14 a0d8020787 [update] reformat file.ts & fileModal 2022-12-29 11:41:28 +08:00
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
lai8928 a9c59953f4 [fix] package.json lint command error 2022-12-09 11:12:30 +08:00
lai8928 93badd5ecf add mode input form 2022-12-09 11:09:13 +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
peterlu14 8a13967797 [update] eslint format autofix 2022-11-30 16:10:49 +08:00
peterlu14 9544edd65a [update] fix TS(2307) error 2022-11-30 14:59:10 +08:00
peterlu14 4cbb90feda [update] windiCSS & default port 8082 & path 2022-11-30 14:57:43 +08:00
peterlu14 730143f816 [update] first commit 2022-11-30 13:30:02 +08:00
peterlu14 28b4a6ec2d Initial commit 2022-11-30 05:19:49 +00:00
297 changed files with 41819 additions and 1233 deletions
+115
View File
@@ -0,0 +1,115 @@
aliases:
- &restore_cache
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- &install_dependencies
name: Install Dependencies
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- &save_cache
name: Save Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
docker: &docker
docker:
- image: cimg/node:16.16.0
defaults: &defaults
<<: *docker
parallelism: 1
working_directory: ~/repo
version: 2
jobs:
test:
<<: *defaults
steps:
- checkout
- restore_cache: *restore_cache
- run: *install_dependencies
- save_cache: *save_cache
- run: yarn test:unit
deploy-staging:
<<: *defaults
steps:
- checkout
- run:
name: Add variables
command:
echo "VUE_APP_INCLUDE_DEMOS=true" >> .env.production.local &&
echo "VUE_APP_BUILD_VERSION=true" >> .env.production.local &&
echo "VUE_APP_GTM_ENABLED=false" >> .env.production.local
- restore_cache: *restore_cache
- run: *install_dependencies
- save_cache: *save_cache
- run: yarn build:ci
- add_ssh_keys:
fingerprints:
- "f8:c0:94:79:68:a2:5e:33:9f:78:bc:ad:ed:86:c6:3c"
- run:
name: Add $SERVER_IP to known_hosts
command: ssh-keyscan -H $SERVER_IP >> ~/.ssh/known_hosts
- run:
name: Deploy to hosting
command: |
tar -czf dist.tar.gz ~/repo/dist -C ~/repo/dist .
scp ~/repo/dist.tar.gz $SERVER_USER@$SERVER_IP:~/tmp
ssh $SERVER_USER@$SERVER_IP /bin/bash << EOF
rm -rf $DEPLOY_PATH/*
tar -xzf ~/tmp/dist.tar.gz -C $DEPLOY_PATH
rm -f ~/tmp/dist.tar.gz
EOF
deploy-production:
<<: *defaults
steps:
- checkout
- run:
name: Add variables
command:
echo "VUE_APP_YANDEX_METRICS_KEY=$YANDEX_METRICS_KEY" >> .env.production.local &&
echo "VUE_APP_DRIFT_KEY=$DRIFT_KEY" >> .env.production.local &&
echo "VUE_APP_GTM_KEY=$GTM_KEY" >> .env.production.local &&
echo "VUE_APP_GTM_ENABLED=true" >> .env.production.local &&
echo "VUE_APP_ROUTER_MODE_HISTORY=$ROUTER_MODE_HISTORY" >> .env.production.local
- restore_cache: *restore_cache
- run: *install_dependencies
- save_cache: *save_cache
- run: yarn build:ci
- run:
name: Deploy to hosting
command: |
tar -czf dist.tar.gz ~/repo/dist -C ~/repo/dist .
ssh-keyscan -t rsa $SERVER_IP >> ~/.ssh/known_hosts
scp ~/repo/dist.tar.gz $SERVER_USERNAME@$SERVER_IP:~/
ssh $SERVER_USERNAME@$SERVER_IP << EOF
mkdir -p tmp
rm -rf $PRODUCTION_DEPLOY_PATH/*
tar -xzf ~/dist.tar.gz -C $PRODUCTION_DEPLOY_PATH
rm -rf ~/dist.tar.gz ~/tmp
EOF
workflows:
version: 2
build-and-deploy:
jobs:
# - test
- deploy-staging:
# requires:
# - test
context: vuestic-admin-staging
filters:
branches:
only: develop
- deploy-production:
# requires:
# - test
context: vuestic-production
filters:
branches:
only: master
+7
View File
@@ -0,0 +1,7 @@
node_modules/
dist/
.dockerignore
.gitattributes
.gitignore
Dockerfile
README.md
+9
View File
@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
+7
View File
@@ -0,0 +1,7 @@
VITE_APP_GTM_KEY=
VITE_APP_INCLUDE_DEMOS=
VITE_APP_ROUTER_MODE_HISTORY=
VITE_APP_BUILD_VERSION=
-28
View File
@@ -1,28 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true,
// 新增 fix module export
node: true,
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended',
// 新增,必须放在最后面
// 'airbnb',
// 'prettier',
'plugin:prettier/recommended',
],
overrides: [],
// 新增
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 'latest',
// 新增
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: ['vue', '@typescript-eslint'],
rules: {},
}
+34
View File
@@ -0,0 +1,34 @@
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
'vue/setup-compiler-macros': true,
},
plugins: ['@typescript-eslint'],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
sourceType: 'module',
ecmaVersion: 2021,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'@vue/typescript/recommended',
'@vue/prettier',
],
rules: {
'@typescript-eslint/no-empty-interface': 0,
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'prettier/prettier': ['warn', {}, { usePrettierrc: true }],
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/no-explicit-any': 0, // allow explicit any's because of the legacy code and ts-less deps, but still prohibit IMplicit any's
'vue/multi-word-component-names': 0,
'vue/no-lone-template': 0,
},
}
+4
View File
@@ -22,3 +22,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# Local Netlify folder
.netlify
netlify.toml
+1
View File
@@ -0,0 +1 @@
_
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
+1
View File
@@ -0,0 +1 @@
16
+18
View File
@@ -0,0 +1,18 @@
# Ignore everything recursively...
*
# But not the .{ts,js,html,css,scss,vue,json,md} files
!*.ts
!*.js
!*.html
!*.css
!*.scss
!*.vue
!*.json
!*.md
# But still ignore the dist folder
dist/**
# Check subdirectories too
!*/
+8 -7
View File
@@ -1,9 +1,10 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"semi": false,
"trailingComma": "all",
"bracketSpacing": true
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "all",
"bracketSpacing": true,
"printWidth": 120,
"vueIndentScriptAndStyle": true
}
+73
View File
@@ -0,0 +1,73 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
+13
View File
@@ -0,0 +1,13 @@
FROM node:14
WORKDIR /app
COPY . /app
EXPOSE 8080
RUN npm install http-server -g
RUN npm install --no-optional && npm run build
CMD http-server ./dist
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Epicmax LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+146
View File
@@ -0,0 +1,146 @@
<p align="center">
<a href="./README.md"> English </a> | <a href="./README.zh-CN.md"> 简体中文 </a> | 日本語
</p>
<h1 align="center"> Vuestic Admin </h1>
<p align="center">
38+のカスタムUIコンポーネントと美しいデザインのVue.js無料管理画面テンプレート</br>
開発者 <a href="https://epicmax.co">Epicmax</a>.
デザイン <a href="#">Vasili Savitski</a>
</p>
<p align="center">
<a href="https://vuestic.epicmax.co"> デモ </a> | <a href="[THEME_URL]"> 新しい白いテーマのデモ </a> | <a href="https://github.com/epicmaxco/vuestic-admin/wiki"> 説明書 </a>
</p>
<p align="center">
<a href="https://vuestic.epicmax.co" target="_blank">
<img src="https://i.imgur.com/RMchKNW.jpg" align="center" width="888px"/>
</a>
</p>
<p align="center">
<a href="https://circleci.com/gh/epicmaxco/vuestic-admin">
<img src="https://img.shields.io/circleci/build/github/epicmaxco/vuestic-admin/master" alt="Circleci">
</a>
</p>
<h2>
<a href="[THEME_URL]" target="_blank">
新しい白いテーマのデモ
</a>
</h2>
<p align="center">
<a href="[THEME_URL]" target="_blank">
<img src="https://i.imgur.com/DnFcrjD.png" align="center" width="888px"/>
</a>
</p>
## インストール方法
まず初めに、事前に必要なソフトが全てインストールされていることを確認してください。
- [Node.js](https://nodejs.org/en/) (>=8.9)
- [npm](https://www.npmjs.com/get-npm) バージョン 3+ (あるいは[yarn](https://yarnpkg.com/lang/en/docs/install/#mac-stable)バージョン 1.16+) と[Git](https://git-scm.com/).
必要なソフトウェアがインストールされていることを確認できたら、次に示す簡単なステップに沿って Vuestic Admin をインストールして、ご使用ください。
```
# repoをクローン
$ git clone https://github.com/epicmaxco/vuestic-admin.git myproject
# appディレクトリに移動し、dependenciesをインストール
$ cd myproject
```
npm をご使用の方:
```
$ npm install
# localhost:8080(デフォルト)でホットリロードを使ってサーブ
$ npm run dev
# プロダクション用にビルド
$ npm run build
# プロダクション用にビルドして、バンドルアナライザーレポートを閲覧
$ npm run build --report
```
yarn をご使用の方:
```
$ yarn install
# localhost:8080(デフォルト)でホットリロードを使ってサーブ
$ yarn dev
# プロダクション用にビルド
$ yarn build
# プロダクション用にビルドして、バンドルアナライザーレポートを閲覧
$ yarn build --report
```
## 機能
[レスポンシブレイアウト](https://vuestic.epicmax.co/#/admin/dashboard) |
[チャート (Chart.js)](https://vuestic.epicmax.co/#/admin/statistics/charts) |
[プログレスバー](https://vuestic.epicmax.co/#/admin/statistics/progress-bars) |
[フォーム](https://vuestic.epicmax.co/#/admin/forms/form-elements) |
[セレクト](https://vuestic.epicmax.co/#/admin/forms/form-elements) |
[デートピッカー](https://vuestic.epicmax.co/#/admin/forms/form-elements) |
[チェックボックス・ラジオ](https://vuestic.epicmax.co/#/admin/forms/form-elements) |
[スタティックテーブル・データテーブル](https://vuestic.epicmax.co/#/admin/tables/data) |
[medium エディタ](https://vuestic.epicmax.co/#/admin/forms/medium-editor) |
[スムーズタイポグラフィ](https://vuestic.epicmax.co/#/admin/ui/typography) |
[ボタン](https://vuestic.epicmax.co/#/admin/ui/buttons) |
[折りたたみ](https://vuestic.epicmax.co/#/admin/ui/collapses) |
[カラーピッカー](https://vuestic.epicmax.co/#/admin/ui/color-pickers) |
[タイムライン](https://vuestic.epicmax.co/#/admin/ui/timelines) |
[トースト](https://vuestic.epicmax.co/#/admin/ui/notifications) |
[ツールチップ](https://vuestic.epicmax.co/#/admin/ui/popovers) |
[ポップオーバー](https://vuestic.epicmax.co/#/admin/ui/popovers) |
[アイコン](https://vuestic.epicmax.co/#/admin/ui/icons/) |
[スピナー](https://vuestic.epicmax.co/#/admin/ui/spinners) |
[モーダル](https://vuestic.epicmax.co/#/admin/ui/modals) |
[ファイルアップロード](https://vuestic.epicmax.co/#/admin/ui/file-upload) |
[チップ](https://vuestic.epicmax.co/#/admin/ui/chips) |
[ツリー](https://vuestic.epicmax.co/#/admin/ui/tree-view) |
[カード ](https://vuestic.epicmax.co/#/admin/ui/cards) |
[レーティング](https://vuestic.epicmax.co/#/admin/ui/rating) |
[スライダー](https://vuestic.epicmax.co/#/admin/ui/sliders) |
[チャット](https://vuestic.epicmax.co/#/admin/ui/chat) |
[マップ (MapLibre, Yandex, Leaflet, amMap)](https://vuestic.epicmax.co/#/admin/maps/maplibre-maps) |
[ログイン/登録ページテンプレート](https://vuestic.epicmax.co/#/auth/login) |
[404 ページテンプレート](https://vuestic.epicmax.co/#/admin/pages/404-pages) |
[i18n](https://vuestic.epicmax.co/#/admin/dashboard)
## ブラウザサポート
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| IE11, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 支援メンバー
素晴らしい PR、課題、アイデアをご提供いただきいつもありがとうございます。[参加](https://github.com/epicmaxco/vuestic-admin/blob/master/.github/CONTRIBUTING.md)いただける方を常に受け付けています!
[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/0)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/0)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/1)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/1)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/2)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/2)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/3)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/3)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/4)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/4)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/5)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/5)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/6)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/6)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/7)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/7)
## あなた方に仕事を依頼することはできますか?
はい!一声おかけください:[hello@epicmax.co](mailto:hello@epicmax.co) </br>
ぜひ一緒に仕事をしましょう!その他これまでやってきた仕事 - [epicmax.co](https://epicmax.co)
## ソーシャルネットワーク
Vuestic の最新ニュースを受け取ろう![Twitter](https://twitter.com/epicmaxco)や [Facebook](https://facebook.com/epicmaxco)でフォローしてください。
## ライセンス
[MIT](https://github.com/epicmaxco/vuestic-admin/blob/master/LICENSE) license
+182 -9
View File
@@ -1,16 +1,189 @@
# Vue 3 + TypeScript + Vite
<p align="center">
<a href="https://vuestic.dev" target="_blank">
<img alt="Vuestic UI Logo" width="500" src="./public/vuestic-admin-logo.png">
</a>
</p>
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
<p align="center">
Free and beautiful Vue.js admin template with 44+ custom UI components.</br>
Developed by <a href="https://epicmax.co">Epicmax</a>.
Designed by <a href="#">Vasili Savitski</a>
<br>
Based on <a href="https://github.com/epicmaxco/vuestic-ui">Vuestic UI</a> library.
</p>
## Recommended IDE Setup
<p align="center">
<a href="https://vuestic.epicmax.co/admin/dashboard?theme=light"> Light theme demo </a> | <a href="https://vuestic.epicmax.co/admin/dashboard?theme=semi-dark">Semi-dark theme demo </a> | <a href="https://vuestic.epicmax.co/admin/dashboard?theme=original">Original theme demo </a> | <a href="https://vuestic.dev"> Vuestic UI documentation </a>
</p>
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
> Vuestic Admin is built with Vuestic UI. See our
> <a href="https://github.com/epicmaxco/vuestic-ui/issues">issues</a>,
> <a href="https://vuestic.dev/en/contribution/guide">contributing guide</a> and join discussions on our
> <a href="https://discord.gg/jTKTjj2weV">discord server</a> to help us improve Vuestic Admin & Vuestic UI experience.
## Type Support For `.vue` Imports in TS
<p align="center">
<a href="https://vuestic.epicmax.co" target="_blank">
<img src="./public/vuestic-admin-image.jpg" align="center" width="888px"/>
</a>
</p>
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
<p align="center">
<a href="https://circleci.com/gh/epicmaxco/vuestic-admin">
<img src="https://img.shields.io/circleci/build/github/epicmaxco/vuestic-admin/master" alt="Circleci">
</a>
</p>
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
### Documentation
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
Documentation, guides and tutorials are available
on <a href="https://vuestic.dev">vuestic.dev</a>
### Official Discord Server
Ask questions at the official
community [discord server](https://discord.gg/jTKTjj2weV)
### Installation
First of all, make sure you have all prerequisites installed:
- [Node.js](https://nodejs.org/en/) ( >=14.\*)
- [npm](https://www.npmjs.com/get-npm) version 3+ (
or [yarn](https://yarnpkg.com/lang/en/docs/install) version 1.16+)
and [Git](https://git-scm.com).
After checking the prerequisites, follow these simple steps to install and use
Vuestic Admin:
```
# clone the repo
$ git clone https://github.com/epicmaxco/vuestic-admin.git myproject
# go into app's directory and install dependencies:
$ cd myproject
```
Then, if you use npm:
```
$ npm install
# serve with hot reload at localhost:8080 by default.
$ npm run dev
# build for production
$ npm run build
# build for production and view the bundle analyzer report.
$ npm run build --report
```
If you use yarn:
```
$ yarn install
# serve with hot reload at localhost:8080 by default.
$ yarn dev
# build for production
$ yarn build
# build for production and view the bundle analyzer report.
$ yarn build --report
```
### Features
[Responsive layout](https://vuestic.epicmax.co/admin/dashboard) |
[charts (Chart.js)](https://vuestic.epicmax.co/admin/statistics/charts) |
[progress bars](https://vuestic.epicmax.co/admin/statistics/progress-bars) |
[forms](https://vuestic.epicmax.co/admin/forms/form-elements) |
[selects](https://vuestic.epicmax.co/admin/forms/form-elements) |
[date pickers](https://vuestic.epicmax.co/admin/forms/form-elements) |
[checkboxes and radios](https://vuestic.epicmax.co/admin/forms/form-elements)
|
[static tables and data tables](https://vuestic.epicmax.co/admin/tables/data)
|
[medium editor](https://vuestic.epicmax.co/admin/forms/medium-editor) |
[smooth typography](https://vuestic.epicmax.co/admin/ui/typography) |
[buttons](https://vuestic.epicmax.co/admin/ui/buttons) |
[collapses](https://vuestic.epicmax.co/admin/ui/collapses) |
[color pickers](https://vuestic.epicmax.co/admin/ui/color-pickers) |
[timelines](https://vuestic.epicmax.co/admin/ui/timelines) |
[toasts](https://vuestic.epicmax.co/admin/ui/notifications) |
[tooltips](https://vuestic.epicmax.co/admin/ui/popovers) |
[popovers](https://vuestic.epicmax.co/admin/ui/popovers) |
[icons](https://vuestic.epicmax.co/admin/ui/icons/) |
[spinners](https://vuestic.epicmax.co/admin/ui/spinners) |
[modals](https://vuestic.epicmax.co/admin/ui/modals) |
[file upload](https://vuestic.epicmax.co/admin/ui/file-upload) |
[chips](https://vuestic.epicmax.co/admin/ui/chips) |
[trees](https://vuestic.epicmax.co/admin/ui/tree-view) |
[cards](https://vuestic.epicmax.co/admin/ui/cards) |
[ratings](https://vuestic.epicmax.co/admin/ui/rating) |
[sliders](https://vuestic.epicmax.co/admin/ui/sliders) |
[chat](https://vuestic.epicmax.co/admin/ui/chat) |
[maps (MapLibre, Yandex, Leaflet, amMap)](https://vuestic.epicmax.co/admin/maps/maplibre-maps)
|
[login/signup pages templates](https://vuestic.epicmax.co/auth/login) |
[404 pages templates](https://vuestic.epicmax.co/admin/pages/404-pages) |
[i18n](https://vuestic.epicmax.co/admin/dashboard)
### Browser Support
We design Vuestic Admin to support the latest modern web browsers.
| <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" /><br>Edge | <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" /><br>Firefox | <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" /><br>Chrome | <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" /><br>Safari | <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" /><br>Opera |
| ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
### Contributing
Thanks for all your wonderful PRs, issues and ideas.
<a href="https://github.com/epicmaxco/vuestic-admin/graphs/contributors">
<img src="https://opencollective.com/vuestic-admin/contributors.svg?width=890&button=false" />
</a>
<br>
Youre always welcome to join: check out
our <a href="https://vuestic.dev/en/contribution/guide">
contribution guides</a>
, [open issues](https://github.com/epicmaxco/vuestic-ui/issues)
and [discord server](https://discord.gg/jTKTjj2weV)
### Partners & Sponsors ❤️
<div>
<a href="https://epicmax.co/" target="_blank" title="Epicmax - Top Vue.js Development Company"><img src="./.github/assets/epicmax.svg" loading="lazy" vspace="10" alt=""></a>
<a href="https://www.ag-grid.com/" target="_blank" title=" Data Grid: AG Grid: High-Performance React Grid, Angular Grid, JavaScript Grid"><img hspace="15" vspace="10" src="./.github/assets/aggrid.svg" loading="lazy" alt=""></a>
<a href="https://vuejobs.com/" target="_blank" title="Vue.js jobs Browse through dozens of Vue.js openings"><img src="./.github/assets/vuejobs.svg" loading="lazy" vspace="10" alt=""></a>
<a href="https://flatlogic.com/" target="_blank" title="Flatlogic - The best way to create React, Angular and Vue full-stack web applications"><img hspace="15" vspace="10" src="./.github/assets/flatlogic.svg" loading="lazy" alt=""></a>
</div>
<br >
Become a partner: [hello@epicmax.co](mailto:hello@epicmax.co)
## Can I hire you guys?
Yes! Say hi: [hello@epicmax.co](mailto:hello@epicmax.co) </br>
We will be happy to work with you! Other [work weve done](https://epicmax.co)
### Awards
<a href="https://flatlogic.com/templates/vuestic-vue-free-admin" target="_blank">
<img src="https://i.imgur.com/ZeQPZ3Q.png" align="center" width="150px"/>
</a>
<p>
By <a href="https://flatlogic.com/templates/vuestic-vue-free-admin" target="_blank">@flatlogic</a> marketplace
</p>
### Follow us
Stay up to date with the latest Vuestic news! Follow us
on [Twitter](https://twitter.com/epicmaxco)
or [Facebook](https://facebook.com/epicmaxco)
### License
[MIT](https://github.com/epicmaxco/vuestic-admin/blob/master/LICENSE) license.
+146
View File
@@ -0,0 +1,146 @@
<p align="center">
<a href="./README.md"> English </a> | 简体中文 | <a href="./README.ja-JP.md"> 日本語 </a>
</p>
<h1 align="center"> Vuestic Admin </h1>
<p align="center">
免费与美妙Vue.js管理模板包括38以上个定制用户界面组件</br>
由<a href="https://epicmax.co">Epicmax</a>开发。
由<a href="#">Vasili Savitski</a>设计
</p>
<p align="center">
<a href="https://vuestic.epicmax.co"> 演示版 </a> | <a href="[THEME_URL]"> 新白色主题演示 </a> | <a href="https://github.com/epicmaxco/vuestic-admin/wiki"> 文档 </a>
</p>
<p align="center">
<a href="https://vuestic.epicmax.co" target="_blank">
<img src="https://i.imgur.com/hVxoYQM.jpg" align="center" width="888px"/>
</a>
</p>
<p align="center">
<a href="https://circleci.com/gh/epicmaxco/vuestic-admin">
<img src="https://img.shields.io/circleci/build/github/epicmaxco/vuestic-admin/master" alt="Circleci">
</a>
</p>
<h2>
<a href="[THEME_URL]" target="_blank">
新白色主题演示
</a>
</h2>
<p align="center">
<a href="[THEME_URL]" target="_blank">
<img src="https://i.imgur.com/DnFcrjD.png" align="center" width="888px"/>
</a>
</p>
## 安装
首先确定 ni 安装所有的先决条件:
- [Node.js](https://nodejs.org/en/) (>=8.9)
- [npm](https://www.npmjs.com/get-npm) 3+版本 (或许[yarn](https://yarnpkg.com/lang/en/docs/install/#mac-stable) 1.16+版本) 和[Git](https://git-scm.com/)。
检查先决条件之后,用简单指令之下安装与应用 Vuestic 管理台:
```
#克隆存储库
$ git clone https://github.com/epicmaxco/vuestic-admin.git myproject
#进入应用目录与安装依赖
$ cd myproject
```
之后,如果您用 npm:
```
$ npm install
#默认情况下在localhost:8080用热重载
$ npm run dev
#构建生产
$ npm run build
#构建生产与查看包分析报表
$ npm run build --report
```
如果您用 yarn:
```
$ yarn install
#默认情况下在localhost:8080用热重载
$ yarn dev
#构建生产
$ yarn build
#构建生产与查看包分析报表
$ yarn build --report
```
## 功能
[响应布局](https://vuestic.epicmax.co/#/admin/dashboard) |
[图表(Charts.js)](https://vuestic.epicmax.co/#/admin/statistics/charts) |
[进度表](https://vuestic.epicmax.co/#/admin/statistics/progress-bars) |
[表格](https://vuestic.epicmax.co/#/admin/forms/form-elements) |
[选辑](https://vuestic.epicmax.co/#/admin/forms/form-elements) |
[日期选择器](https://vuestic.epicmax.co/#/admin/forms/form-elements) |
[复选框和单选框](https://vuestic.epicmax.co/#/admin/forms/form-elements) |
[静态表与数据表](https://vuestic.epicmax.co/#/admin/tables/data) |
[medium editor](https://vuestic.epicmax.co/#/admin/forms/medium-editor) |
[平滑设计字体](https://vuestic.epicmax.co/#/admin/ui/typography) |
[按钮](https://vuestic.epicmax.co/#/admin/ui/buttons) |
[塌缩](https://vuestic.epicmax.co/#/admin/ui/collapses) |
[颜色选择器](https://vuestic.epicmax.co/#/admin/ui/color-pickers) |
[时间线](https://vuestic.epicmax.co/#/admin/ui/timelines) |
[土司通知](https://vuestic.epicmax.co/#/admin/ui/notifications) |
[工具提示](https://vuestic.epicmax.co/#/admin/ui/popovers) |
[弹窗](https://vuestic.epicmax.co/#/admin/ui/popovers) |
[图标](https://vuestic.epicmax.co/#/admin/ui/icons/) |
[自旋体](https://vuestic.epicmax.co/#/admin/ui/spinners) |
[模式](https://vuestic.epicmax.co/#/admin/ui/modals) |
[文件上传](https://vuestic.epicmax.co/#/admin/ui/file-upload) |
[厚切薯条通知](https://vuestic.epicmax.co/#/admin/ui/chips) |
[](https://vuestic.epicmax.co/#/admin/ui/tree-view) |
[卡片](https://vuestic.epicmax.co/#/admin/ui/cards) |
[等级](https://vuestic.epicmax.co/#/admin/ui/rating) |
[滑动器](https://vuestic.epicmax.co/#/admin/ui/sliders) |
[聊天系统](https://vuestic.epicmax.co/#/admin/ui/chat) |
[地图(MapLibre, Yandex, Leaflet, amMap)](https://vuestic.epicmax.co/#/admin/maps/maplibre-maps) |
[登录/注册页模板](https://vuestic.epicmax.co/#/auth/login) |
[404 页模板](https://vuestic.epicmax.co/#/admin/pages/404-pages) |
[i18n](https://vuestic.epicmax.co/#/admin/dashboard)
## 支持的浏览器
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| IE11, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 参与贡献
多谢您的出色公关、问题与注意。随时欢迎您的[加入](https://github.com/epicmaxco/vuestic-admin/blob/master/.github/CONTRIBUTING.md)!
[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/0)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/0)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/1)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/1)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/2)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/2)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/3)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/3)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/4)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/4)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/5)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/5)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/6)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/6)[![](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/images/7)](https://sourcerer.io/fame/smartapant/epicmaxco/vuestic-admin/links/7)
## 我可以雇佣你们吗?
当然!给[hello@epicmax.co](mailto:hello@epicmax.co)发电子邮件 </br>
和您一起工作我们将很高兴!查看我们其它项目 - [epicmax.co](https://epicmax.co)
## 关注我们
敬请期待 Vuestic 最新消息!关注我们[Twitter](https://twitter.com/epicmaxco)与[Facebook](https://facebook.com/epicmaxco)
## 许可证
[MIT](https://github.com/epicmaxco/vuestic-admin/blob/master/LICENSE)许可证.
+16
View File
@@ -0,0 +1,16 @@
### 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)
+7
View File
@@ -0,0 +1,7 @@
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
})
+39
View File
@@ -0,0 +1,39 @@
'use strict'
const path = require('path')
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report,
},
dev: {
env: require('./dev.env'),
port: process.env.PORT || 8080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false,
},
}
+4
View File
@@ -0,0 +1,4 @@
'use strict'
module.exports = {
NODE_ENV: '"production"',
}
+50
View File
@@ -0,0 +1,50 @@
# Pre-production
## SEO
We have a boierplate prepared with some analytics ingrained. This includes:
- [Yandex Metrica](https://metrica.yandex.com/about)
To get these running - just provide keys to respective apis. You're advised to copy `.env.example` with rename to `.env` then modify it.
Notice, that if you are about to use Google Maps then you also have to provide it with your personal API key. The key must be defined under the `VUE_APP_GOOGLE_MAPS_API_KEY` environment-variable (more on them below) and can be obtained [here](https://developers.google.com/maps/documentation/javascript/get-api-key).
## Deploy
We use [circleci](https://circleci.com) to deploy vuestic version you're able to see on demo.
If you want to save some time and use our config, do notice that circleci will need the following keys, that you have to set in **Build Settings -> Environment Variables**.
- `DEPLOY_PASSWORD` ssh password.
- `DEPLOY_PATH_PRODUCTION` production build will be loaded to this folder.
- `DEPLOY_PATH_STAGING` staging build will be loaded to this folder.
- `DEPLOY_URL` ssh url.
- `DEPLOY_USER` ssh password.
You can modify [config](../.circleci/config.yml) if our solution doesn't suit your needs exactly.
Couple of things to note:
- in `.env` file keys should look like this `VUE_APP_DRIFT_KEY`. Which will correspond to circleci key `DRIFT_KEY`. You essentially have two ways to pass config into build process.
- Circleci will run tests before both staging and production.
## Demos
You can enable demos in build by:
```
VUE_APP_INCLUDE_DEMOS=true
```
Demos are included in staging build by default. They're not present in production because of significant impact on bundle size.
## Build Version
You can enable build version, hash commit, and timestamp by build to the main page footer:
```
VUE_APP_BUILD_VERSION=true
```
This information are excluded by default.
+7 -10
View File
@@ -2,17 +2,14 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
<link
href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,400;1,700&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
/>
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400" rel="stylesheet" />
<!-- <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> -->
<!-- <script src="https://cdn.plot.ly/plotly-2.20.0.min.js" charset="utf-8"></script> -->
<link rel="icon" href="/favicon.ico" />
<title>Vuestic Admin</title>
</head>
<body>
<div id="app"></div>
+7870
View File
File diff suppressed because it is too large Load Diff
+67 -24
View File
@@ -1,36 +1,79 @@
{
"name": "vite-project",
"name": "vuestic-admin",
"private": true,
"version": "0.0.0",
"type": "module",
"version": "3.1.0",
"scripts": {
"prepare": "husky install",
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix",
"format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\""
"build": "npm run lint && vue-tsc --noEmit && vite build",
"build:ci": "vite build",
"prelint": "npm run format",
"lint": "eslint --fix \"./src/**/*.{ts,js,vue}\"",
"format": "prettier --write .",
"preview": "vite preview"
},
"lint-staged": {
"./src/**/*.{ts,js,vue}": [
"npm run lint"
]
},
"dependencies": {
"pinia": "^2.0.27",
"vite-plugin-windicss": "^1.8.8",
"vue": "^3.2.41",
"@amcharts/amcharts5": "^5.3.6",
"@amcharts/amcharts5-geodata": "^5.1.0",
"@fortawesome/fontawesome-free": "^6.3.0",
"@gtm-support/vue-gtm": "^2.0.0",
"@hyjiacan/vue-slideout": "^3.0.2",
"axios": "^1.3.4",
"chart.js": "^4.2.1",
"epic-spinners": "^2.0.0",
"flag-icons": "^6.6.6",
"ionicons": "^4.6.3",
"leaflet": "^1.9.3",
"leaflet-map": "^0.2.1",
"maplibre-gl": "^2.4.0",
"material-icons": "^1.13.7",
"medium-editor": "^5.23.3",
"npm-check-updates": "^16.7.10",
"pinia": "^2.0.33",
"plotly.js-dist-min": "^2.23.2",
"register-service-worker": "^1.7.2",
"sass": "^1.58.3",
"v-contextmenu": "^3.0.0",
"vue": "^3.2.47",
"vue-chartjs": "^5.2.0",
"vue-good-table-next": "^0.2.1",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",
"vuestic-ui": "^1.5.2",
"windicss": "^3.5.6"
"vue-yandex-maps": "^0.12.3",
"vuestic-ui": "^1.6.1"
},
"devDependencies": {
"@types/node": "^18.11.11",
"@typescript-eslint/eslint-plugin": "^5.45.1",
"@typescript-eslint/parser": "^5.45.1",
"@vitejs/plugin-vue": "^3.2.0",
"eslint": "^8.29.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"@intlify/vite-plugin-vue-i18n": "^6.0.3",
"@types/leaflet": "^1.9.1",
"@types/medium-editor": "^5.0.5",
"@types/node": "^18.14.6",
"@typescript-eslint/eslint-plugin": "^5.54.1",
"@typescript-eslint/parser": "^5.54.1",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.1.0",
"@vue/eslint-config-typescript": "^11.0.2",
"autoprefixer": "^10.4.13",
"echarts": "^5.4.1",
"eslint": "^8.35.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.8.0",
"prettier": "^2.8.0",
"typescript": "*",
"vite": "^3.2.3",
"vue-tsc": "^1.0.9"
"eslint-plugin-vue": "^9.9.0",
"husky": "^8.0.3",
"lint-staged": "^13.1.2",
"mqtt": "^4.3.7",
"postcss": "^8.4.21",
"prettier": "^2.8.4",
"typescript": "^4.9.5",
"unocss": "^0.51.12",
"vite": "^4.1.4",
"vite-plugin-windicss": "^1.8.10",
"vue-echarts": "^6.5.4",
"vue-eslint-parser": "^9.1.0",
"vue-tsc": "^1.2.0",
"windicss": "^3.5.6"
}
}
+4936 -893
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {},
},
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

+15 -15
View File
@@ -1,19 +1,19 @@
<script setup lang="ts"></script>
<template>
<div><router-view></router-view></div>
<router-view />
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
<style lang="scss">
@import 'scss/main.scss';
#app {
font-family: 'Source Sans Pro', Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
body {
margin: 0;
background: white;
}
</style>
+12
View File
@@ -0,0 +1,12 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1765_7055)">
<path d="M0 0H512V512H0V0Z" fill="#222D3A"/>
<path d="M330.085 110.955C311.299 90.672 285.059 79.5024 256.097 79.5024C226.981 79.5024 200.655 90.6044 181.955 110.762C163.053 131.141 153.843 158.838 156.005 188.746C160.292 247.751 205.192 295.75 256.097 295.75C307.003 295.75 351.826 247.76 356.18 188.765C358.371 159.128 349.103 131.489 330.085 110.955Z" fill="#B3BAC0"/>
<path d="M53.9275 511.997H458.333C459 503 458.206 483.499 456.333 473.141C448.185 427.941 422.757 389.972 382.789 363.327C347.282 339.675 302.305 326.642 256.13 326.642C209.956 326.642 164.978 339.666 129.471 363.327C89.5038 389.982 64.0754 427.951 55.9275 473.15C54.0546 483.509 53.5001 504.5 53.9275 511.997Z" fill="#B3BAC0"/>
</g>
<defs>
<clipPath id="clip0_1765_7055">
<rect width="512" height="512" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_56_271" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
<rect width="24" height="24" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_56_271)">
<path d="M7 21C6.45 21 5.97917 20.8042 5.5875 20.4125C5.19583 20.0208 5 19.55 5 19V6H4V4H9V3H15V4H20V6H19V19C19 19.55 18.8042 20.0208 18.4125 20.4125C18.0208 20.8042 17.55 21 17 21H7ZM17 6H7V19H17V6ZM9 17H11V8H9V17ZM13 17H15V8H13V17Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_52_158" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
<rect width="24" height="24" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_52_158)">
<path d="M19.5 11V13.5H18.5V11V10.5H18H15.5V9.5H18H18.5V9V6.5H19.5V9V9.5H20H22.5V10.5H20H19.5V11ZM2.5 18V18.5H3H15H15.5V18V17.2C15.5 16.9288 15.4309 16.6712 15.2891 16.4393C15.1536 16.2175 14.9702 16.0392 14.7428 15.9129L14.7333 15.9076L14.7236 15.9028C13.7925 15.4372 12.8493 15.0863 11.8942 14.8519C10.939 14.6174 9.97395 14.5 9 14.5C8.02605 14.5 7.06104 14.6174 6.10581 14.8519C5.15072 15.0863 4.20748 15.4372 3.27639 15.9028L3.26668 15.9076L3.25718 15.9129C3.0298 16.0392 2.84639 16.2175 2.71086 16.4393C2.56911 16.6712 2.5 16.9288 2.5 17.2V18ZM9 11.5C8.03366 11.5 7.21887 11.1618 6.52855 10.4714C5.83824 9.78113 5.5 8.96634 5.5 8C5.5 7.03366 5.83824 6.21887 6.52855 5.52855C7.21887 4.83824 8.03366 4.5 9 4.5C9.96634 4.5 10.7811 4.83824 11.4714 5.52855C12.1618 6.21887 12.5 7.03366 12.5 8C12.5 8.96634 12.1618 9.78113 11.4714 10.4714C10.7811 11.1618 9.96634 11.5 9 11.5ZM1.5 19.5V17.2C1.5 16.7241 1.62109 16.2941 1.86359 15.8991C2.10825 15.5007 2.42772 15.2027 2.82681 14.9956C3.82714 14.4957 4.84065 14.1222 5.86769 13.8735C6.89573 13.6245 7.93956 13.5 9 13.5C10.0604 13.5 11.1043 13.6245 12.1323 13.8735C13.1594 14.1222 14.1729 14.4957 15.1732 14.9956C15.5723 15.2027 15.8918 15.5007 16.1364 15.8991C16.3789 16.2941 16.5 16.7241 16.5 17.2V19.5H1.5ZM9 10.5C9.68366 10.5 10.2814 10.2507 10.7661 9.76605C11.2507 9.28137 11.5 8.68366 11.5 8C11.5 7.31634 11.2507 6.71863 10.7661 6.23395C10.2814 5.74926 9.68366 5.5 9 5.5C8.31634 5.5 7.71863 5.74926 7.23395 6.23395C6.74926 6.71863 6.5 7.31634 6.5 8C6.5 8.68366 6.74926 9.28137 7.23395 9.76605C7.71863 10.2507 8.31634 10.5 9 10.5Z" fill="#424242" stroke="#424242"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

-38
View File
@@ -1,38 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
+53
View File
@@ -0,0 +1,53 @@
<template>
<svg class="va-icon-vuestic" :height="height" viewBox="0 0 478 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M18.2761 56.1386L0 13.3794H10.5519L18.8968 33.3107L27.2417 13.3794H37.7936L19.5175 56.1386H18.2761ZM62.4836 40.8281V13.3794H72.5527V39.3108C72.5527 44.8281 75.1734 46.7592 78.6907 46.7592C82.5528 46.7592 84.9666 44.8281 84.9666 39.3108V13.3794H95.1047V40.8281C95.1047 50.6213 87.7942 56.1386 78.6907 56.1386C69.2423 56.1386 62.4836 50.3454 62.4836 40.8281ZM122.415 54.7593V13.3794H149.795V22.3451H132.553V29.7935H148.485V38.6211H132.553V46.0695H150.554V54.7593H122.415ZM175.933 42.6212H185.795C185.795 45.035 187.727 46.9661 190.071 46.9661C192.623 46.9661 194.209 45.5178 194.209 43.3798C194.209 40.2763 190.14 39.3798 186.761 38.2074C179.795 35.7246 175.933 32.4831 175.933 25.3796C175.933 18.0692 182.209 12.0001 190.002 12.0001C199.175 12.0001 203.52 17.8623 204.072 25.5175H194.554C194.554 23.1727 193.106 21.2416 190.278 21.2416C188.071 21.2416 185.933 22.552 185.933 25.2417C185.933 28.3452 189.727 28.8969 193.244 29.9314C200.623 32.1383 204.072 36.2763 204.072 42.7591C204.072 50.0696 197.865 56.1386 190.071 56.1386C181.037 56.1386 175.933 50.0696 175.933 42.6212ZM236.442 54.7593V22.3451H227.2V13.3794H256.028V22.3451H246.649V54.7593H236.442ZM281.408 54.7593V13.3794H291.546V54.7593H281.408ZM317.502 34.0694C317.502 21.7244 326.812 12.0001 339.916 12.0001C347.778 12.0001 353.226 14.9657 357.64 19.5864L350.675 26.2072C347.778 23.3796 344.054 21.5865 339.916 21.5865C332.536 21.5865 327.778 27.1038 327.778 34.0694C327.778 41.035 332.536 46.5523 339.916 46.5523C344.054 46.5523 347.778 44.7592 350.675 41.9315L357.433 48.5523C353.295 52.8972 347.571 56.1386 339.916 56.1386C326.812 56.1386 317.502 46.4143 317.502 34.0694Z"
fill="url(#paint0_linear)"
/>
<path
d="M388.671 23.5893L388.364 24.844H391.71L391.403 23.5893C391.18 22.7157 390.957 21.7677 390.734 20.7454C390.511 19.7231 390.288 18.7565 390.065 17.8457H389.953C389.749 18.7751 389.535 19.751 389.312 20.7733C389.107 21.777 388.894 22.7157 388.671 23.5893ZM381.7 32.3999L387.221 14.2769H393.02L398.541 32.3999H393.466L392.574 28.5523H387.5L386.607 32.3999H381.7Z"
:fill="colorsComputed.start"
/>
<path
d="M403.987 32.3999V14.2769H409.34C410.715 14.2769 411.97 14.4441 413.104 14.7787C414.256 15.1133 415.241 15.6431 416.059 16.368C416.877 17.0929 417.509 18.0223 417.955 19.1561C418.42 20.29 418.652 21.6562 418.652 23.2548C418.652 24.8533 418.429 26.2288 417.983 27.3812C417.537 28.5337 416.905 29.4817 416.087 30.2252C415.288 30.9687 414.331 31.517 413.215 31.8702C412.119 32.2234 410.92 32.3999 409.619 32.3999H403.987ZM408.782 28.5523H409.061C409.712 28.5523 410.325 28.4779 410.901 28.3292C411.477 28.1619 411.97 27.8831 412.379 27.4928C412.806 27.1024 413.141 26.5634 413.383 25.8756C413.624 25.1879 413.745 24.3143 413.745 23.2548C413.745 22.1953 413.624 21.3309 413.383 20.6618C413.141 19.9926 412.806 19.4721 412.379 19.1004C411.97 18.7286 411.477 18.4777 410.901 18.3476C410.325 18.1989 409.712 18.1245 409.061 18.1245H408.782V28.5523Z"
:fill="colorsComputed.start"
/>
<path
d="M424.664 32.3999V14.2769H429.794L432.526 21.6934C432.694 22.1953 432.852 22.7157 433 23.2548C433.149 23.7938 433.307 24.3328 433.474 24.8719H433.586C433.753 24.3328 433.911 23.7938 434.06 23.2548C434.208 22.7157 434.366 22.1953 434.534 21.6934L437.155 14.2769H442.285V32.3999H437.935V27.0467C437.935 26.582 437.954 26.0708 437.991 25.5132C438.047 24.9555 438.103 24.3886 438.158 23.8124C438.214 23.2176 438.279 22.6414 438.354 22.0837C438.428 21.5261 438.493 21.0242 438.549 20.5781H438.437L436.932 24.9277L434.701 30.9222H432.136L429.905 24.9277L428.456 20.5781H428.344C428.4 21.0242 428.465 21.5261 428.539 22.0837C428.614 22.6414 428.679 23.2176 428.734 23.8124C428.79 24.3886 428.837 24.9555 428.874 25.5132C428.93 26.0708 428.957 26.582 428.957 27.0467V32.3999H424.664Z"
:fill="colorsComputed.start"
/>
<path d="M450.028 32.3999V14.2769H454.823V32.3999H450.028Z" :fill="colorsComputed.start" />
<path
d="M462.567 32.3999V14.2769H467.474L471.489 22.3625L473.218 26.4333H473.329C473.274 25.95 473.209 25.4202 473.134 24.844C473.06 24.2678 472.995 23.6823 472.939 23.0875C472.883 22.4741 472.828 21.87 472.772 21.2752C472.735 20.6618 472.716 20.0762 472.716 19.5186V14.2769H477.289V32.3999H472.381L468.366 24.2864L466.638 20.2435H466.526C466.638 21.2845 466.768 22.4183 466.917 23.6451C467.065 24.8719 467.14 26.0429 467.14 27.1582V32.3999H462.567Z"
:fill="colorsComputed.start"
/>
<defs>
<linearGradient id="paint0_linear" x1="0" y1="56.1386" x2="357.64" y2="56.1386" gradientUnits="userSpaceOnUse">
<stop :stop-color="colorsComputed.end" />
<stop offset="1" :stop-color="colorsComputed.start" />
</linearGradient>
</defs>
</svg>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useColors } from 'vuestic-ui'
const { getColor, shiftHSLAColor } = useColors()
const props = withDefaults(
defineProps<{
height?: number | string
color?: string
}>(),
{
height: 16,
color: 'primary',
},
)
const colorsComputed = computed(() => {
const color = getColor(props.color, 'primary')
return { start: color, end: shiftHSLAColor(color, { l: -20 }) }
})
</script>
@@ -0,0 +1,196 @@
<template>
<div>
<div class="mt-2 mx-1 grid grid-cols-6 items-center">
<div>
<p>Unit ({{ unitObj.text }})</p>
</div>
<va-button-toggle
v-model="unitObj.text"
:options="unitOption"
@update:model-value="changeUnit"
></va-button-toggle>
</div>
<va-divider />
<div class="mt-2 mx-1 grid grid-cols-6 items-center">
<div class="col-span-1">
<p>Space</p>
</div>
<va-input
v-model="leftBoundryInput"
:label="axis === 'x' ? 'Left' : 'Bottom'"
placeholder="10%"
class="col-span-2"
@keyup.enter="setBoundry"
@blur="setBoundry"
/>
<va-input
v-model="rightBoundryInput"
:label="axis === 'x' ? 'Right' : 'Top'"
placeholder="10%"
class="px-1 col-span-2"
@keyup.enter="setBoundry"
@blur="setBoundry"
/>
<div class="mx-2 col-span-1 justify-self-end">
<button @click="resetBoundry">Reset</button>
</div>
</div>
<va-divider />
<div class="mt-2 mx-1 grid grid-cols-6 items-center">
<div class="col-span-1">
<p>Fixed</p>
</div>
<va-input
v-model="minInput"
class="col-span-2"
label="Min"
@keyup.enter="setMin(minInput)"
@blur="setMin(minInput)"
>
<template #appendInner>{{ unitObj.text }}</template>
</va-input>
<va-input
v-model="maxInput"
class="px-1 col-span-2"
label="Max"
@keyup.enter="setMax(maxInput)"
@blur="setMax(maxInput)"
>
<template #appendInner>{{ unitObj.text }}</template>
</va-input>
<div class="mx-2 col-span-1 justify-self-end">
<button small @click="resetMinMax">Reset</button>
</div>
</div>
<va-divider />
<div class="mt-2 mx-1 grid grid-cols-6 items-center">
<div>
<p>Interval</p>
</div>
<va-input
v-model="intervalInput"
class="col-span-2"
@keyup.enter="setInterval(Number(intervalInput))"
@blur="setInterval(Number(intervalInput))"
>
<template #appendInner>{{ unitObj.text }}</template>
</va-input>
<div class="mx-2 col-span-3 justify-self-end">
<button small @click="resetInterval">Reset</button>
</div>
</div>
<!-- <va-divider />
<div class="mt-2 mx-1 grid grid-cols-6 items-center">
<div>
<p>SplitLine</p>
</div>
<va-counter v-model="splitNumber" class="col-span-2" @update:model-value="setSplitNumber" />
</div> -->
</div>
<va-divider />
</template>
<script setup lang="ts">
import { ref, computed, toRef, defineProps } from 'vue'
import { XAxisManager } from '@/utils/chart/x-axis/xAxisManager'
import { YAxisManager } from '@/utils/chart/y-axis/yAxisManager'
/* init definition */
const props = defineProps<{
axis: string
axisManager: XAxisManager | YAxisManager
axisIndex: number
dataZoom: object
}>()
const axisManager = toRef(props, 'axisManager')
const dataZoom = toRef(props, 'dataZoom')
/** axis setting */
/* unit */
const unitObj = computed(() => axisManager.value.getUnit())
const unitOption = computed(() =>
Object.keys(unitObj.value.option).map((ele) => {
return { label: ele, value: ele }
}),
)
const changeUnit = function (_unit: string) {
/* caculate the ratio */
const oldScale = unitObj.value.scale
const newScale = unitObj.value.option[_unit]
const ratio = newScale / oldScale
/* correct the input value with ratio */
if (maxInput.value !== '') maxInput.value = String(Number(maxInput.value) / ratio)
if (minInput.value !== '') minInput.value = String(Number(minInput.value) / ratio)
if (intervalInput.value !== '') intervalInput.value = String(Number(intervalInput.value) / ratio)
/* change the scale */
unitObj.value.scale = unitObj.value.option[_unit]
}
/* boundry */
const leftBoundryInput = ref('')
const rightBoundryInput = ref('')
const setBoundry = function () {
if (leftBoundryInput.value === '') leftBoundryInput.value = '0'
if (rightBoundryInput.value === '') rightBoundryInput.value = '0'
axisManager.value.setBoundry([leftBoundryInput.value, rightBoundryInput.value])
dataZoom.value[0].disabled = true
}
const resetBoundry = function () {
leftBoundryInput.value = ''
rightBoundryInput.value = ''
axisManager.value.resetBoundry()
dataZoom.value[0].disabled = false
}
/* max & min */
const maxInput = ref('')
const minInput = ref('')
const setMax = function (max: string) {
if (max === '') return
/* adjust max with scale */
max = String(Number(max) * unitObj.value.scale)
/* set max */
axisManager.value.setMax(max)
dataZoom.value[0].disabled = true
}
const setMin = function (min: string) {
if (min === '') return
/* adjust min with scale */
min = String(Number(min) * unitObj.value.scale)
/* set min */
axisManager.value.setMin(min)
dataZoom.value[0].disabled = true
}
const resetMinMax = function () {
maxInput.value = ''
minInput.value = ''
axisManager.value.resetMinMax()
dataZoom.value[0].disabled = false
}
/* interval */
const intervalInput = ref('')
const setInterval = function (interval: number) {
if (interval <= 0) return
interval = Number(interval) * unitObj.value.scale
axisManager.value.setInterval(interval)
}
const resetInterval = function () {
intervalInput.value = ''
axisManager.value.resetInterval()
}
/* splitLine */
const splitNumber = ref(5)
const setSplitNumber = function (splitNumber: number) {
axisManager.value.setSplitNumber(splitNumber)
}
</script>
<style></style>
@@ -0,0 +1,46 @@
<template>
<slideout v-model="show" :show-mask="false" :arrow-button="false" :title="title" :size="'30%'" dock="left" resizable>
<div class="grid justify-items-center self-center text-lg">
<span>X-Axis</span>
</div>
<va-divider />
<AxisSetting
axis="x"
:axis-manager="chartStore.chartManager.children[chartIndex].xAxisManager"
:data-zoom="chartStore.chartManager.children[chartIndex].dataZoom"
:axis-index="0"
></AxisSetting>
<div class="grid justify-items-center self-center text-lg">
<span>Y-Axis</span>
</div>
<va-divider />
<AxisSetting
axis="y"
:axis-manager="chartStore.chartManager.children[chartIndex].yAxisManager"
:data-zoom="chartStore.chartManager.children[chartIndex].dataZoom"
:axis-index="0"
></AxisSetting>
</slideout>
</template>
<script setup lang="ts">
import AxisSetting from './AxisSetting.vue'
import { ref, computed } from 'vue'
import { useChartStore } from '@/stores/data-analysis/chart'
const title = 'Axis Settings'
const chartStore = useChartStore()
const chartIndex = ref(0)
/* slideout show */
const show = computed({
// getter
get() {
return chartStore.xAxisSlider
},
// setter
set(newValue) {
chartStore.xAxisSlider = newValue
},
})
</script>
@@ -0,0 +1,428 @@
<template>
<div class="">
<v-contextmenu ref="contextmenu">
<v-contextmenu-item @click="changeAxisPointer">pointer</v-contextmenu-item>
<v-contextmenu-item v-if="canvasInstance.format === 'line'" @click="changeFormat('scatter')"
>scatter</v-contextmenu-item
>
<v-contextmenu-item v-if="canvasInstance.format === 'scatter'" @click="changeFormat('line')"
>line</v-contextmenu-item
>
<v-contextmenu-item @click="deleteSelected">clear</v-contextmenu-item>
<v-contextmenu-item @click="download">download</v-contextmenu-item>
</v-contextmenu>
<va-card v-contextmenu:contextmenu class="h-full">
<va-card-content class="h-full">
<div style="position: relative" class="h-full">
<canvas
id="lineCanvas"
class="myCanvas"
:width="width"
:height="height"
style="position: absolute; top: 0; left: 0"
></canvas>
<canvas
id="scatterCanvas"
class="myCanvas displayNone"
:width="width"
:height="height"
style="position: absolute; top: 0; left: 0"
></canvas>
<canvas
id="axisCanvas"
class="myCanvas"
:width="width"
:height="height"
style="position: absolute; top: 0; left: 0"
></canvas>
</div>
</va-card-content>
</va-card>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed, onMounted } from 'vue'
import { useCanvasStore } from '@/stores/data-analysis/canvas'
import api from '@/data/server-api'
import { useToast } from 'vuestic-ui'
const { init } = useToast()
const canvasStore = useCanvasStore()
const { width, height, numTicks, innerWidth, innerHeight, start, end, xTickPixels, yTickPixels, x, y } =
canvasStore.canvas
// monitor screen height
const screenHeight = computed(() => canvasStore.canvas.screenHeight)
// monitor screen width
const screenWidth = computed(() => canvasStore.canvas.screenWidth)
// monitor screen inner height
const screenInnerHeight = computed(() => canvasStore.canvas.screenInnerHeight)
// monitor screen inner width
const screenInnerWidth = computed(() => canvasStore.canvas.screenInnerWidth)
// line or scatter
const format = computed(() => canvasStore.canvas.format)
// canvas instance
const canvasInstance = canvasStore.canvas
// show axis pointer
const showAxisPointer = ref(false)
let axisCanvas: HTMLCanvasElement
let axisCtx: CanvasRenderingContext2D
let lineCanvas: HTMLCanvasElement
let lineCtx: CanvasRenderingContext2D
let scatterCanvas: HTMLCanvasElement
let scatterCtx: CanvasRenderingContext2D
// show axis pointer or not
const changeAxisPointer = function () {
showAxisPointer.value = !showAxisPointer.value
reDraw()
}
// change line or scatter
const changeFormat = function (format: string) {
canvasInstance.format = format
}
// delect select rect
const deleteSelected = function () {
canvasInstance.resetSelected()
reDraw()
}
const download = function () {
// create combined canvas
const combinedCanvas = document.createElement('canvas')
const combinedCtx = combinedCanvas.getContext('2d')
// if the source canvas is exist
if (combinedCtx && lineCanvas && scatterCanvas) {
// define height & width
combinedCanvas.width = axisCanvas.width
combinedCanvas.height = axisCanvas.height
// make background white
combinedCtx.fillStyle = '#FFFFFF'
combinedCtx.fillRect(0, 0, combinedCanvas.width, combinedCanvas.height)
// draw axis
combinedCtx.drawImage(axisCanvas, 0, 0)
combinedCtx.globalCompositeOperation = 'multiply'
// draw line
if (format.value === 'line') combinedCtx.drawImage(lineCanvas, 0, 0)
// draw scatter
if (format.value === 'scatter') combinedCtx.drawImage(scatterCanvas, 0, 0)
// create download link
const link = document.createElement('a')
link.download = 'canvas.png'
link.href = combinedCanvas.toDataURL()
link.click()
}
}
// mouse down position
let mouseStartX = -1
let mouseStartY = -1
// mouse moving temp position
let mouseTempX = -1
let mouseTempY = -1
// mouse up position
let mouseEndX = -1
let mouseEndY = -1
// dragging flag
let isDragging = false
// color code
let colorIndex = 0
const generatorColor = function () {
const colorCode = [
'#054A91',
'#5FA8D3',
'#16697A',
'#2A9D8F',
'#746C68',
'#E9C46A',
'#F78514',
'#D14958',
'#4C4DC8',
'#9E2A2B',
'#F87575',
]
const ret = colorCode[colorIndex]
colorIndex = colorIndex === colorCode.length ? 0 : colorIndex + 1
return ret
}
const calcPositionX = function (xPos: number) {
const diffX = xPos - x.range[0]
let ratioX = Math.abs(diffX / (x.range[1] - x.range[0]))
if (ratioX > 1) ratioX = 1
return Math.trunc(ratioX * innerWidth) + start.x
}
const calcPositionY = function (yPoint: number) {
const diffY = y.range[1] - yPoint
let ratioY = Math.abs(diffY / (y.range[1] - y.range[0]))
if (ratioY > 1) ratioY = 1
return Math.trunc(ratioY * innerHeight) + end.y
}
const drawPoint = function (posX: number, posY: number, color: string, ctx: CanvasRenderingContext2D) {
// draw circle & fill color
ctx.beginPath()
ctx.arc(posX, posY, 2, 0, 2 * Math.PI)
ctx.fillStyle = color
ctx.fill()
}
// Helper function to draw the x axis with tick marks
function drawXAxis(xMin: number, xMax: number, tickPixels: number[], ctx: CanvasRenderingContext2D) {
// draw axis line
ctx.beginPath()
ctx.moveTo(start.x, start.y)
ctx.lineTo(end.x, start.y)
ctx.fillStyle = 'black'
ctx.lineWidth = 1
ctx.font = '40px sans-serif'
ctx.textAlign = 'center'
// draw axis tickle
for (let i = 0; i < tickPixels.length; i++) {
const x = tickPixels[i]
ctx.moveTo(start.x + x, start.y)
ctx.lineTo(start.x + x, start.y + 15)
const text = canvasInstance.getXText(xMin + (i / numTicks) * (xMax - xMin))
ctx.fillText(text, start.x + x, start.y + 75)
}
ctx.strokeStyle = 'black'
ctx.stroke()
}
function drawYAxis(yMin: number, yMax: number, tickPixels: number[], ctx: CanvasRenderingContext2D) {
// draw axis line
ctx.beginPath()
ctx.moveTo(start.x, start.y)
ctx.lineTo(start.x, end.y)
ctx.fillStyle = 'black'
ctx.font = '40px sans-serif'
ctx.textAlign = 'right'
ctx.lineWidth = 1
// draw axis tickle
for (let i = 0; i < tickPixels.length; i++) {
const y = tickPixels[i]
ctx.moveTo(start.x, start.y - y)
ctx.lineTo(start.x - 25, start.y - y)
const text = canvasInstance.getYText(yMin + (i / numTicks) * (yMax - yMin))
ctx.fillText(text, start.x - 30, start.y - y + 15)
}
ctx.strokeStyle = 'black'
ctx.stroke()
}
const drawXAxisPointer = function (mouseX: number, ctx: CanvasRenderingContext2D) {
const x = (mouseX * width) / screenWidth.value
ctx.moveTo(x, start.y)
ctx.lineTo(x, end.y)
ctx.strokeStyle = 'black'
ctx.stroke()
const value = canvasInstance.mouseXToValue(mouseX)
const text = canvasInstance.getXText(value)
ctx.textAlign = 'center'
ctx.fillText(text, x, start.y + 50)
}
const drawYAxisPointer = function (mouseY: number, ctx: CanvasRenderingContext2D) {
const y = (mouseY * height) / screenHeight.value
ctx.moveTo(start.x, y)
ctx.lineTo(end.x, y)
ctx.strokeStyle = 'black'
ctx.stroke()
const value = canvasInstance.mouseYToValue(mouseY)
const text = canvasInstance.getYText(value)
ctx.textAlign = 'right'
ctx.fillText(text, start.x, y)
}
const drawInputData = function (x: string | any[], y: number[]) {
const color = generatorColor()
// draw line
lineCtx.beginPath()
lineCtx.moveTo(calcPositionX(x[0]), calcPositionY(y[0]))
for (let i = 0; i < x.length; i++) {
const X = calcPositionX(x[i])
const Y = calcPositionY(y[i])
lineCtx.lineTo(X, Y)
// draw point
drawPoint(X, Y, color, scatterCtx)
}
lineCtx.lineWidth = 2
lineCtx.strokeStyle = color
lineCtx.stroke()
}
const generateAxis = function () {
drawXAxis(Number(x.range[0]), Number(x.range[1]), xTickPixels, axisCtx)
drawYAxis(Number(y.range[0]), Number(y.range[1]), yTickPixels, axisCtx)
}
const resetCanvas = function () {
axisCtx.clearRect(0, 0, width, height)
lineCtx?.clearRect(0, 0, width, height)
scatterCtx?.clearRect(0, 0, width, height)
}
const outOfBoundry = function (mouseX: number, mouseY: number) {
if (mouseX < canvasInstance.getBoundryInScreen(3) || mouseX > canvasInstance.getBoundryInScreen(1)) return true
if (mouseY < canvasInstance.getBoundryInScreen(0) || mouseY > canvasInstance.getBoundryInScreen(2)) return true
return false
}
const reDraw = function () {
const rect = axisCanvas.getBoundingClientRect()
// Clear the canvas
axisCtx.clearRect(0, 0, width, height)
// Draw the x and y axes with tick marks
generateAxis()
// Draw the x and y axis pointers
if (showAxisPointer.value === true) {
drawXAxisPointer(mouseTempX, axisCtx)
drawYAxisPointer(mouseTempY, axisCtx)
}
for (const selectionRect of canvasInstance.selectedSave) {
axisCtx.lineWidth = 5
axisCtx.strokeRect(
(selectionRect[0] / rect.width) * width,
(selectionRect[1] / rect.height) * height,
((selectionRect[2] - selectionRect[0]) / rect.width) * width,
((selectionRect[3] - selectionRect[1]) / rect.height) * height,
)
}
if (isDragging || (mouseEndX !== -1 && mouseEndY !== -1)) {
if (isDragging === true) {
mouseEndX = mouseTempX
mouseEndY = mouseTempY
}
axisCtx.lineWidth = 5
axisCtx.strokeRect(
(mouseStartX / rect.width) * width,
(mouseStartY / rect.height) * height,
((mouseEndX - mouseStartX) / rect.width) * width,
((mouseEndY - mouseStartY) / rect.height) * height,
)
}
}
onMounted(() => {
axisCanvas = document.getElementById('axisCanvas') as HTMLCanvasElement
axisCtx = axisCanvas?.getContext('2d')
lineCanvas = document.getElementById('lineCanvas') as HTMLCanvasElement
lineCtx = lineCanvas?.getContext('2d')
scatterCanvas = document.getElementById('scatterCanvas') as HTMLCanvasElement
scatterCtx = scatterCanvas?.getContext('2d')
const rect = axisCanvas?.getBoundingClientRect()
if (rect) {
canvasInstance.setScreenSize(rect.width, rect.height)
}
// resize
window.addEventListener('resize', () => {
const rect = axisCanvas?.getBoundingClientRect()
if (rect) canvasInstance.setScreenSize(rect.width, rect.height)
})
// listen for mouse down event
axisCanvas?.addEventListener('mousedown', (event) => {
if (event.button === 0) {
const rect = axisCanvas.getBoundingClientRect()
// reset mouse up
mouseEndX = -1
mouseEndY = -1
// set starting coordinates of square
mouseStartX = event.clientX - rect.left
mouseStartY = event.clientY - rect.top
if (outOfBoundry(mouseStartX, mouseStartY) === true) return
isDragging = true
}
})
// listen for mouse up event
axisCanvas?.addEventListener('mouseup', async () => {
const rect = axisCanvas.getBoundingClientRect()
if (mouseEndX === -1 || mouseEndY === -1) return
canvasInstance.selectedSave.push([mouseStartX, mouseStartY, mouseEndX, mouseEndY])
// update ending coordinates of square
const dataStartX =
((mouseStartX - rect.width * 0.1) * (x.range[1] - x.range[0])) / screenInnerWidth.value + Number(x.range[0])
const dataEndX =
((mouseEndX - rect.width * 0.1) * (x.range[1] - x.range[0])) / screenInnerWidth.value + Number(x.range[0])
const dataStartY =
((rect.height - mouseStartY - rect.height * 0.1) * (y.range[1] - y.range[0])) / screenInnerHeight.value +
Number(y.range[0])
const dataEndY =
((rect.height - mouseEndY - rect.height * 0.1) * (y.range[1] - y.range[0])) / screenInnerHeight.value +
Number(y.range[0])
const xInfo = [canvasInstance.x.channel.id, dataStartX, dataEndX]
const yInfo = [canvasInstance.y.channel.id, dataStartY, dataEndY]
const ret = await api.meta.findMeta(canvasStore.canvas.metaIds, [xInfo, yInfo])
console.log('ret', ret)
for (const mes of ret.data) {
init(mes[1])
}
// reset mouse position
mouseStartX = mouseStartY = mouseEndX = mouseEndY = -1
// reset isDraggable flag
isDragging = false
})
axisCanvas?.addEventListener('mousemove', function (event) {
const rect = axisCanvas.getBoundingClientRect()
// get mouse current pos
mouseTempX = event.clientX - rect.left
mouseTempY = event.clientY - rect.top
if (outOfBoundry(mouseTempX, mouseTempY) === true) return
reDraw()
})
})
watch(format, (newVal: string) => {
if (newVal === 'line') {
lineCanvas?.classList.remove('displayNone')
scatterCanvas?.classList.add('displayNone')
}
if (newVal === 'scatter') {
lineCanvas?.classList.add('displayNone')
scatterCanvas?.classList.remove('displayNone')
}
})
defineExpose({ reDraw, generateAxis, drawInputData, resetCanvas })
</script>
<style>
.myCanvas {
width: 100%;
height: 100%;
}
.displayNone {
display: none;
}
</style>
@@ -0,0 +1,106 @@
<template>
<div>
<va-card class="h-full">
<va-card-content class="h-full w-full">
<EChart ref="chart_ref" :option="chart" :autoresize="true" :loading="loading" />
</va-card-content>
</va-card>
<AxisSettingSlider></AxisSettingSlider>
</div>
</template>
<script setup lang="ts">
import { ECharts, EChartsCoreOption } from 'echarts'
import { ref, onMounted } from 'vue'
import { useChartStore } from '@/stores/data-analysis/chart'
import { useCanvasStore } from '@/stores/data-analysis/canvas'
import AxisSettingSlider from './AxisSettingSlider.vue'
import Chart from '@/utils/chart/chart'
const chartStore = useChartStore()
const canvasStore = useCanvasStore()
const chartIndex = 0
interface Props {
chart: Chart
}
const props = defineProps<Props>()
const chart_ref = ref<ECharts | null>(null)
const setOption = function (option: EChartsCoreOption, notMerge = false) {
chart_ref.value?.setOption(option, notMerge)
}
const appendData = function (seriesIndex: number, data: Array<number | string>) {
if (data.length <= 5e4) {
chartStore.chartManager.children[chartIndex].series[seriesIndex].data.push(...data)
} else {
for (const _data of data) {
chartStore.chartManager.children[chartIndex].series[seriesIndex].data.push(_data)
}
}
// chart_ref.value?.appendData({
// seriesIndex: seriesIndex,
// data: data,
// })
// chart_ref.value?.resize()
setTimeout(() => {
const chartXRange = chart_ref.value.chart.getModel().getComponent('xAxis').axis.scale._extent
const chartYRange = chart_ref.value.chart.getModel().getComponent('yAxis').axis.scale._extent
canvasStore.canvas.x.range = chartXRange.map((val: number) => String(val))
canvasStore.canvas.y.range = chartYRange.map((val: number) => String(val))
})
}
const markLine = function (value: number | Array<number> | string, seriesIndex = 0) {
chartStore.chartManager.children[chartIndex].series[seriesIndex].markLine.data = value
// chart_ref.value?.setOption({
// series: [
// {
// markLine: {
// data: [{ name: header, yAxis: value }],
// },
// },
// ],
// })
}
/* refresh */
const reset = function () {
setOption({}, true)
}
/* Loading */
const loading = ref(false)
const startLoading = function () {
loading.value = true
}
const stopLoading = function () {
loading.value = false
}
onMounted(() => {
const newTool = {
show: true,
title: 'custom icon1',
icon: 'path://M432.45,595.444c0,2.177-4.661,6.82-11.305,6.82c-6.475,0-11.306-4.567-11.306-6.82s4.852-6.812,11.306-6.812C427.841,588.632,432.452,593.191,432.45,595.444L432.45,595.444z M421.155,589.876c-3.009,0-5.448,2.495-5.448,5.572s2.439,5.572,5.448,5.572c3.01,0,5.449-2.495,5.449-5.572C426.604,592.371,424.165,589.876,421.155,589.876L421.155,589.876z M421.146,591.891c-1.916,0-3.47,1.589-3.47,3.549c0,1.959,1.554,3.548,3.47,3.548s3.469-1.589,3.469-3.548C424.614,593.479,423.062,591.891,421.146,591.891L421.146,591.891zM421.146,591.891',
onclick: function () {
chartStore.xAxisSlider = !chartStore.xAxisSlider
},
}
for (const chart of chartStore.chartManager.children) {
chart.toolbox.feature['myTool1'] = newTool
}
})
defineExpose({
startLoading,
stopLoading,
setOption,
appendData,
markLine,
reset,
})
</script>
<style lang=""></style>
@@ -0,0 +1,54 @@
<template>
<div>
<div>
X-Axis
<va-select
v-model="channelStore.xAxisSelected"
:text-by="'name'"
:options="xOption"
:no-options-text="'Select a file first.'"
></va-select>
</div>
<div>
Y-Axis
<va-select
v-model="channelStore.yAxisSelected"
:text-by="'name'"
:options="yOption"
:no-options-text="'Select a file first.'"
></va-select>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, watch } from 'vue'
import { useChannelStore } from '@/stores/data-analysis/channel'
const channelStore = useChannelStore()
const xOption = computed(() => channelStore.xAxisOptions)
const yOption = computed(() => channelStore.yAxisOptions)
watch(
xOption,
() => {
if (Object.keys(channelStore.xAxisSelected).length === 0) {
channelStore.xAxisSelected = xOption.value[1]
}
},
{
deep: true,
},
)
watch(
yOption,
() => {
if (Object.keys(channelStore.yAxisSelected).length === 0) {
channelStore.yAxisSelected = yOption.value[2]
}
},
{
deep: true,
},
)
</script>
@@ -0,0 +1,44 @@
<template>
<va-card>
<va-card-content>
<va-button @click="showModal">FILE</va-button>
<p>X: {{ channelStore.xAxisSelected.name }} Y: {{ channelStore.yAxisSelected.name }}</p>
<va-list class="py-2" fit>
<va-list-label> </va-list-label>
<template v-for="(file, i) in selectedFiles" :key="'item' + file.id">
<va-list-item>
<va-list-item-section>
<va-list-item-label>
{{ file.name }}
</va-list-item-label>
</va-list-item-section>
</va-list-item>
<va-list-separator v-if="i < selectedFiles.length - 1" :key="'separator' + i" class="my-1" fit />
</template>
</va-list>
<Suspense>
<FileSlide ref="file_modal_ref"></FileSlide>
</Suspense>
</va-card-content>
</va-card>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useChannelStore } from '@/stores/data-analysis/channel'
import { useFileViewStore } from '@/stores/data-analysis/file-view'
import FileSlide from './FileSlider.vue'
const channelStore = useChannelStore()
const fileViewStore = useFileViewStore()
const selectedFiles = reactive(fileViewStore.selectedFiles)
/* File Modal Handling */
const file_modal_ref = ref<InstanceType<typeof FileSlide> | null>(null)
const showModal = function () {
file_modal_ref.value?.showModal()
}
defineExpose({ file_modal_ref, showModal })
</script>
@@ -0,0 +1,147 @@
<template>
<slideout v-model="showSlideOut" title="The title" :size="'120%'" dock="left" resizable>
<ChannelSelector></ChannelSelector>
<div class="pa-1 d-flex align-center">
<va-button-group class="mr-1">
<va-button @click="changeViewFormat(0)">Table</va-button>
<va-button disabled @click="changeViewFormat(1)">Tree</va-button>
</va-button-group>
<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>
<va-inner-loading :loading="loading">
<div>
<TableFormat
v-if="viewFormat === 0"
ref="table_format_ref"
:columns="columns"
:rows="rows"
@on-select="onSelect"
></TableFormat>
<TreeFormat v-if="viewFormat === 1" ref="table_format_ref" :columns="columns" :rows="rows"></TreeFormat>
</div>
</va-inner-loading>
</slideout>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { useFileViewStore } from '@/stores/data-analysis/file-view'
import { useChannelStore } from '@/stores/data-analysis/channel'
import { MetaFile } from '@/utils/file'
import TableFormat from './TableFormat.vue'
import TreeFormat from './TreeFormat.vue'
import ChannelSelector from './ChannelSelector.vue'
import configTable from '@/data/config-table'
const table_format_ref = ref<InstanceType<typeof TableFormat> | null>(null)
const getWorkingMode = function (rowObj) {
if (rowObj.parameter) return configTable.getModeConfig(rowObj.parameter._LIBRARY_, rowObj.parameter.MODE).name
return
}
const getDate = function (rowObj) {
if (rowObj.created_at) {
return rowObj.created_at.replace('Z', '').replace('T', ' ')
}
return
}
const fileViewStore = useFileViewStore()
const channelStore = useChannelStore()
const fileView = reactive(fileViewStore.fileView)
const rows: any = fileView.children
const columns = reactive([
{
label: 'Name',
field: 'name',
},
{
label: 'Working Mode',
field: getWorkingMode,
},
{
label: 'TotalTime',
field: 'time',
type: 'number',
},
{
label: 'Size',
field: 'size',
type: 'number',
},
{
label: 'Created At',
field: getDate,
},
])
const loading = ref(true)
const showSlideOut = ref(false)
const showModal = function () {
showSlideOut.value = !showSlideOut.value
setTimeout(() => {
loading.value = false
}, 2000)
}
const viewFormat = ref(0)
const changeViewFormat = function (view: number) {
viewFormat.value = view
}
const changeFileView = async function (view: string) {
loading.value = true
fileView.changeView(view)
await fileView.appendChildren(1)
loading.value = false
}
const expandAll = function (expandOrNot: boolean) {
if (expandOrNot === true) table_format_ref.value?.expandAll()
if (expandOrNot === false) table_format_ref.value?.collapseAll()
}
const onSelect = function (selectedFile: MetaFile[]) {
fileViewStore.selectedFiles.length = 0
fileViewStore.selectedFiles.push(...selectedFile)
if (fileViewStore.selectedFiles.length > 0) {
// TODO get all selected library
const config: any = configTable.getModeConfig(
fileViewStore.selectedFiles[0].device.library_name,
fileViewStore.selectedFiles[0].parameter.MODE,
)
const channelKey = Object.keys(config.channels)
const channel = Object.values(config.channels)
const channelOptions = channel.map((v: any, idx) => {
v.id = channelKey[idx]
if (v.name === 'Time') {
v.id = 'Time'
}
return v
})
channelStore.xAxisOptions.length = 0
channelStore.xAxisOptions.push(...channelOptions)
channelStore.yAxisOptions.length = 0
channelStore.yAxisOptions.push(...channelOptions)
}
}
onMounted(() => {
fileView.appendChildren()
})
defineExpose({
showModal,
})
</script>
<style lang="scss"></style>
@@ -0,0 +1,64 @@
<template>
<VueGoodTable
ref="vue_good_table_refs"
:columns="columns"
:rows="rows"
:select-options="{ enabled: true, selectAllByGroup: true }"
:search-options="{ enabled: true }"
:group-options="{
enabled: true,
collapsable: true,
rowKey: 'id',
}"
@selected-rows-change="select"
>
</VueGoodTable>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { MetaFile } from '@/utils/file'
// emit
const emit = defineEmits(['onSelect'])
// props
defineProps({
columns: {
type: Array,
default: () => {
return []
},
},
rows: {
type: Array,
default: () => {
return []
},
},
})
// ref
const vue_good_table_refs = ref()
const expandAll = function () {
vue_good_table_refs.value.expandAll()
}
const collapseAll = function () {
vue_good_table_refs.value.collapseAll()
}
const select = function (params: { selectedRows: MetaFile[] }) {
emit('onSelect', params.selectedRows)
}
onMounted(() => {
// Due to "vue_good_table" package error, need to fix group select by reassign function
vue_good_table_refs.value.toggleSelectGroup = function (event: { checked: any }, headerRow: { children: any[] }) {
headerRow.children.forEach((row: { [x: string]: any }) => {
row['vgtSelected'] = event.checked
})
}
})
defineExpose({ expandAll, collapseAll })
</script>
<style></style>
@@ -0,0 +1,55 @@
<template>
<va-tree-view
v-model:checked="selectedNodes"
v-model:expanded="expanedNodes"
:nodes="rows"
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 mr-2" />
<va-icon v-if="node.type === 'Folder' && node.expanded === true" class="fas fa-folder-open mr-2" />
<va-icon v-if="node.type === 'MetaFile'" class="fas fa-file mr-2" />
<template v-if="node.type !== 'MetaFile'">{{ node.name }}</template>
<template v-if="node.type === 'MetaFile'">
{{ node.name }}
</template>
</div>
</template>
</va-tree-view>
</template>
<script setup lang="ts">
import { Ref, ref, computed } from 'vue'
defineProps({
columns: {
type: Array,
default: () => {
return []
},
},
rows: {
type: Array,
default: () => {
return []
},
},
})
const filter = ref('')
const selectedNodes: Ref<(never | string)[]> = ref([])
const expanedNodes: Ref<(never | string)[]> = ref([])
// filter function
const customFilterMethod = computed(() => {
return (node: any, filterText: string) => {
// console.log('customFilterMethod', node, filterText, key, node.name.includes(filterText))
return node.name.includes(filterText)
}
})
</script>
@@ -0,0 +1,178 @@
<template>
<div>
<va-card class="h-full">
<va-card-content>
<va-switch
v-model="daSettingStore.batchMode"
class="mb-1"
size="small"
true-label="Batch Mode"
false-label="InterActive Mode"
></va-switch>
<va-select
v-model="simpleSelectModel"
class="my-1"
:label="'Filter Mode'"
text-by="description"
track-by="id"
:options="simpleOptions"
/>
<div class="my-2">
<div v-if="simpleSelectModel.id === 1">
<va-slider v-model="VTModeModel" label="percentage(%)" track-label-visible :min="0" :max="50" />
</div>
<div v-else-if="simpleSelectModel.id === 2">
<va-slider v-model="slopModeModel" label="1D window" track-label-visible :min="1" :max="50" />
</div>
<div v-else></div>
</div>
<div v-if="daSettingStore.batchMode" class="my-2">
<va-switch
v-model="lineOrScatter"
class="mb-1"
size="small"
true-label="Scatter"
false-label="Line"
></va-switch>
<div>
<h1>Fixed Boundry</h1>
<va-divider></va-divider>
<div class="my-1 grid grid-cols-12 items-center">
<span>x</span>
<va-input v-model="canvasStore.canvas.x.range[0]" class="col-span-5">
<template #appendInner> </template>
</va-input>
<br />
<va-input v-model="canvasStore.canvas.x.range[1]" class="col-span-5"></va-input>
</div>
<div class="my-1 grid grid-cols-12 items-center">
<span>y</span>
<va-input v-model="canvasStore.canvas.y.range[0]" class="col-span-5"></va-input>
<br />
<va-input v-model="canvasStore.canvas.y.range[1]" class="col-span-5"></va-input>
</div>
</div>
</div>
<div>
<va-button class="mt-2" @click="filterData">Start</va-button>
</div>
</va-card-content>
</va-card>
</div>
</template>
<script setup lang="ts">
import { useChannelStore } from '@/stores/data-analysis/channel'
import { useChartStore } from '@/stores/data-analysis/chart'
import { useCanvasStore } from '@/stores/data-analysis/canvas'
import { useDASettingStore } from '@/stores/data-analysis/data-analysis'
import { useFileViewStore } from '@/stores/data-analysis/file-view'
import { Ref, ref, computed } from 'vue'
const emit = defineEmits(['filter-data'])
// chart index
const chartIndex = 0
// create stores
const chartStore = useChartStore()
const channelStore = useChannelStore()
const fileView = useFileViewStore()
const canvasStore = useCanvasStore()
const daSettingStore = useDASettingStore()
// setting
const VTModeModel = ref(10)
const slopModeModel = ref(20)
const simpleOptions = ref([
{ id: 0, description: 'None' },
{
id: 1,
description: 'V-T Mode',
},
{
id: 2,
description: '1st Order Differential',
},
{
id: 3,
description: 'Third option',
},
])
const simpleSelectModel: Ref<{ id: number; description: string }> = ref(simpleOptions.value[0])
const lineOrScatter = ref(false)
// const xOption = computed(() => channelStore.xAxisOptions)
// const yOption = computed(() => channelStore.yAxisOptions)
/* Start */
const filterData = function () {
/* reset specify chart */
const chart = chartStore.chartManager.children[chartIndex]
chart.reset()
/* reset specify canvas */
canvasStore.canvas.reset()
canvasStore.canvas.format = lineOrScatter.value === false ? 'line' : 'scatter'
/* assign mode & library and create series*/
for (const meta of fileView.selectedFiles) {
/* append info to chart */
chart.addLibraryMode(meta.parameter._LIBRARY_, meta.parameter.MODE)
/* append series */
chart.seriesManager.addSeries({ id: meta.id, name: meta.name })
canvasStore.canvas.metaIds.push(meta.id)
}
/* set x-axis name */
chart.xAxisManager.setName(channelStore.xAxisSelected.name)
/* set y-axis name */
chart.yAxisManager.setName(channelStore.yAxisSelected.name)
/* set x-axis label */
chart.xAxisManager.setAxisLabel({
text: channelStore.xAxisSelected.defaultUnit,
scale: channelStore.xAxisSelected.unit[channelStore.xAxisSelected.defaultUnit],
option: channelStore.xAxisSelected.unit,
})
const xChannel = JSON.parse(JSON.stringify(channelStore.xAxisSelected))
canvasStore.canvas.x.channel = xChannel
canvasStore.canvas.x.selectUnit = xChannel.defaultUnit
canvasStore.canvas.x.unit = xChannel.unit
/* set y-axis label */
chart.yAxisManager.setAxisLabel({
text: channelStore.yAxisSelected.defaultUnit,
scale: channelStore.yAxisSelected.unit[channelStore.yAxisSelected.defaultUnit],
option: channelStore.yAxisSelected.unit,
})
const yChannel = JSON.parse(JSON.stringify(channelStore.yAxisSelected))
canvasStore.canvas.y.channel = yChannel
canvasStore.canvas.y.selectUnit = yChannel.defaultUnit
canvasStore.canvas.y.unit = yChannel.unit
emit('filter-data', {
pattern: {
id: simpleSelectModel.value.id,
name: simpleSelectModel.value.description,
parameter: { percentage: VTModeModel.value, window: slopModeModel.value },
},
data: {
id: fileView.selectedFiles.map((ele: any) => ele.id),
channel: [channelStore.xAxisSelected.id, channelStore.yAxisSelected.id],
},
})
}
defineExpose({
simpleSelectModel,
simpleOptions,
VTModeModel,
slopModeModel,
filterData,
})
</script>
<style lang=""></style>
+40
View File
@@ -0,0 +1,40 @@
<template>
<svg class="va-icon-clean-code" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56.02 50.34">
<defs />
<title>overview_icon_4</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path class="cls-1" d="M38.23,16.17a10,10,0,1,0-17.67,6.42V47.5l7.33-5,8,5V22.58A10,10,0,0,0,38.23,16.17Z" />
<path
class="cls-2"
d="M28.23,0a13.15,13.15,0,0,0-9.17,22.6V50.34l8.87-6,9.46,5.92V22.6A13.15,13.15,0,0,0,28.23,0ZM34.4,44.79l-6.54-4.08-5.8,4V24.79a13.11,13.11,0,0,0,12.33,0ZM28.23,23.33A10.17,10.17,0,1,1,38.4,13.17,10.18,10.18,0,0,1,28.23,23.33Z"
/>
<path
class="cls-2"
d="M28.23,5.67a7.5,7.5,0,1,0,7.5,7.5A7.51,7.51,0,0,0,28.23,5.67Zm0,12a4.5,4.5,0,1,1,4.5-4.5A4.5,4.5,0,0,1,28.23,17.67Z"
/>
<polygon class="cls-2" points="9.51 15.11 0 24.61 9.51 34.12 11.63 32 4.24 24.61 11.63 17.23 9.51 15.11" />
<polygon
class="cls-2"
points="46.52 15.11 44.39 17.23 51.78 24.61 44.39 32 46.52 34.12 56.02 24.61 46.52 15.11"
/>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-clean-code {
display: inline-block;
width: 56px;
height: 50px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: #34495e;
}
}
</style>
+38
View File
@@ -0,0 +1,38 @@
<template>
<svg
class="va-icon-color"
:fill="color"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path fill="none" d="M0 0h24v24H0V0z" />
<path
d="M12 22C6.49 22 2 17.51 2 12S6.49 2 12 2s10 4.04 10 9c0 3.31-2.69 6-6 6h-1.77c-.28 0-.5.22-.5.5 0 .12.05.23.13.33.41.47.64 1.06.64 1.67 0 1.38-1.12 2.5-2.5 2.5zm0-18c-4.41 0-8 3.59-8 8s3.59 8 8 8c.28 0 .5-.22.5-.5 0-.16-.08-.28-.14-.35-.41-.46-.63-1.05-.63-1.65 0-1.38 1.12-2.5 2.5-2.5H16c2.21 0 4-1.79 4-4 0-3.86-3.59-7-8-7z"
/>
<circle cx="6.5" cy="11.5" r="1.5" />
<circle cx="9.5" cy="7.5" r="1.5" />
<circle cx="14.5" cy="7.5" r="1.5" />
<circle cx="17.5" cy="11.5" r="1.5" />
</svg>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
color?: string
}>(),
{
color: 'inherit',
},
)
</script>
<style lang="scss">
.va-icon-color {
display: inline-block;
width: 24px;
height: 24px;
}
</style>
+33
View File
@@ -0,0 +1,33 @@
<template>
<svg
xmlns:xlink="http://www.w3.org/1999/xlink"
class="va-icon-faster"
viewBox="0 0 24 24"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<!-- Generator: sketchtool 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>62EBC3B8-A55C-4B01-95A2-52FB8EDD4150</title>
<defs />
<g id="symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon-faster" fill="#34495E">
<g>
<path
id="A"
d="M17.748,19 L16.956,16.3 L12.942,16.3 L12.168,19 L8.928,19 L13.302,6.13 L16.614,6.13 L20.988,19 L17.748,19 Z M14.976,9.064 L14.94,9.064 C14.94,9.064 14.652,10.468 14.418,11.278 L13.68,13.78 L16.218,13.78 L15.498,11.278 C15.264,10.468 14.976,9.064 14.976,9.064 Z"
/>
<rect id="Rectangle-4" x="3" y="11" width="5" height="2" rx="1" />
<rect id="Rectangle-4-Copy" x="4" y="7" width="6" height="2" rx="1" />
<rect id="Rectangle-4" x="2" y="15" width="4" height="2" rx="1" />
</g>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-faster {
width: 24px;
height: 24px;
}
</style>
+46
View File
@@ -0,0 +1,46 @@
<template>
<svg class="va-icon-free" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44.99 51.04">
<defs />
<title>overview_icon_2</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path
class="cls-1"
d="M1.08,28.21C1.08,13.62,8.38,6.29,19,6.29S37,13.69,37,28.21,29.66,50.54,19,50.54,1.08,42.8,1.08,28.21Zm23.56,0c0-11.3-2.58-16.9-5.62-16.9s-5.62,5.6-5.62,16.9S16,41.66,19,41.66,24.65,39.51,24.65,28.21Z"
/>
<line class="cls-2" x1="39.83" y1="47.62" x2="39.83" y2="50.96" />
<path
class="cls-3"
d="M18.73,9.64c-4.9,0-6.9,4.54-6.9,15.66,0,11.29,2.06,16.1,6.9,16.1s6.9-4.81,6.9-16.1C25.63,14.17,23.63,9.64,18.73,9.64Zm0,28.76c-1.07,0-3.9,0-3.9-13.1,0-12.66,2.64-12.66,3.9-12.66s3.9,0,3.9,12.66C22.63,38.4,19.8,38.4,18.73,38.4Z"
/>
<path
class="cls-3"
d="M42.9,43.74A3.76,3.76,0,0,1,40.17,45c-1.95,0-3.24-1.57-3.24-4.4s1.53-4.35,3.29-4.35a3.67,3.67,0,0,1,2.5,1.11l2.08-2.55A6.8,6.8,0,0,0,41.33,33V31h-3v2.12a7.09,7.09,0,0,0-1.64.63,43.71,43.71,0,0,0,.77-8.41c0-15.84-7-25.3-18.73-25.3S0,9.46,0,25.3,7.18,51,18.73,51A16.4,16.4,0,0,0,33.12,43.1,6.77,6.77,0,0,0,40,48.46a6.35,6.35,0,0,0,5-2.22ZM18.73,48C8.88,48,3,39.54,3,25.3S8.73,3,18.73,3s15.73,8.13,15.73,22.3S28.58,48,18.73,48Z"
/>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-free {
display: inline-block;
width: 55px;
height: 47.8px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: none;
stroke: #34495e;
stroke-miterlimit: 10;
stroke-width: 3px;
}
.cls-3 {
fill: #34495e;
}
}
</style>
+34
View File
@@ -0,0 +1,34 @@
<template>
<svg class="va-icon-fresh" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50.98 47.66">
<defs />
<title>overview_icon_5</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path
class="cls-1"
d="M6,19C11,12.66,26.33,3,46.33,6c-3.67,17-8.67,26-8.67,26s-7,14-19.67-3.67C5.67,26.33,9,22.33,6,19Z"
/>
<path
class="cls-2"
d="M48.33.49l-.77,0c-11.22-1.88-30.21,1.46-39,9-3.38,2.89-5,6.11-4.69,9.59a11.06,11.06,0,0,0,4.77,8,11,11,0,0,0,6.24,1.82q.53,0,1.09,0A55.51,55.51,0,0,0,13.2,39.21C9.48,33.07,2.35,30.83,0,30.83v3c.12,0,12.18,1.95,12.5,13.54h0c0,.1,0,.19,0,.29h3a50.57,50.57,0,0,1,3.12-17.21c2.48,5.09,6.36,8,10.91,8.13,5.3.07,10.1-3.85,11.91-9.81.94-3.12,1.88-6.37,2.78-9.51C46.32,11.9,48.31,5,49.62,2.76L51,.49ZM10.28,24.61a8.06,8.06,0,0,1-3.45-5.73c-.19-2.47,1-4.85,3.65-7.08,5.9-5,17.25-8.12,27-8.7-8.35,4-15.7,12.31-20.23,22.51l-.38.1h0A8.82,8.82,0,0,1,10.28,24.61Zm31-6.18c-.9,3.13-1.82,6.37-2.76,9.47-1.4,4.62-4.95,7.69-8.86,7.69h-.13c-4-.07-7.45-3.43-9.33-9.09C25.83,14.27,35.67,4.94,45.92,3.65,44.6,7,43.06,12.39,41.33,18.42Z"
/>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-fresh {
display: inline-block;
width: 51px;
height: 48px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: #34495e;
}
}
</style>
+30
View File
@@ -0,0 +1,30 @@
<template>
<svg class="va-icon-menu" xmlns="http://www.w3.org/2000/svg" width="23" height="18" viewBox="0 0 24 18">
<g fill="none" fill-rule="nonzero" transform="translate(1 -3)">
<path d="M0 0h24v24H0z" />
<rect width="20" height="2" x="2" y="3" :fill="color" rx="1" />
<path :fill="color" d="M11 11h10a1 1 0 0 1 0 2H11a1 1 0 0 1 0-2zM1 11h5a1 1 0 0 1 0 2H1a1 1 0 0 1 0-2z" />
<rect width="20" height="2" x="2" y="19" :fill="color" rx="1" />
<path :stroke="color" stroke-width="2" d="M4 9l-3 3 3 3" />
</g>
</svg>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
color?: string
}>(),
{
color: 'inherit',
},
)
</script>
<style lang="scss">
.va-icon-menu {
display: inline-block;
width: 24px;
height: 24px;
}
</style>
@@ -0,0 +1,32 @@
<template>
<svg class="va-icon-menu-collapsed" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" fill-rule="nonzero">
<path d="M0 0h24v24H0z" />
<rect width="20" height="2" x="2" y="3" :fill="color" rx="1" />
<path
:fill="color"
d="M3 11h10a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2zM20.993 11l-2.7-2.7-1.414 1.414L18.164 11H16a1 1 0 0 0 0 2h2.179l-1.3 1.3 1.414 1.414L21.007 13A1 1 0 0 0 21 11h-.007z"
/>
<rect width="20" height="2" x="2" y="19" :fill="color" rx="1" />
</g>
</svg>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
color?: string
}>(),
{
color: 'inherit',
},
)
</script>
<style lang="scss">
.va-icon-menu-collapsed {
display: inline-block;
width: 24px;
height: 24px;
}
</style>
+27
View File
@@ -0,0 +1,27 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16" :fill="color">
<path
fill-rule="nonzero"
d="M20 2c0-1.1-.9-2-2-2H2C.9 0 0 .9 0 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V2zm-2 0l-8 5-8-5h16zm0 12H2V4l8 5 8-5v10z"
/>
</svg>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
color?: string
}>(),
{
color: 'inherit',
},
)
</script>
<style lang="scss">
.va-icon-message {
display: inline-block;
width: 24px;
height: 24px;
}
</style>
@@ -0,0 +1,27 @@
<template>
<svg class="va-icon-notification" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<path
:fill="color"
d="M10 20c1.1 0 2-.9 2-2H8c0 1.1.9 2 2 2zm6-6V9c0-3.07-1.63-5.64-4.5-6.32V2c0-.83-.67-1.5-1.5-1.5S8.5 1.17 8.5 2v.68C5.64 3.36 4 5.92 4 9v5l-2 2v1h16v-1l-2-2zm-2 1H6V9c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6zM5.58 2.08L4.15.65C1.75 2.48.17 5.3.03 8.5h2a8.445 8.445 0 0 1 3.55-6.42zM17.97 8.5h2c-.15-3.2-1.73-6.02-4.12-7.85l-1.42 1.43a8.495 8.495 0 0 1 3.54 6.42z"
/>
</svg>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
color?: string
}>(),
{
color: 'inherit',
},
)
</script>
<style lang="scss">
.va-icon-notification {
display: inline-block;
width: 24px;
height: 24px;
}
</style>
+29
View File
@@ -0,0 +1,29 @@
<template>
<svg class="va-icon-responsive" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 47.5 49">
<defs />
<title>overview_icon_3</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<polygon class="cls-1" points="37 26 37 7 11 7 11 18 3 18 3 46 11 46 15 46 30 46 37 46 45 46 45 26 37 26" />
<path class="cls-2" d="M40,19V0H8V11H0V49H47.5V19ZM3,46V14H8V46Zm34,0H11V3H37Zm7.5,0H40V22h4.5Z" />
<circle class="cls-2" cx="24" cy="41" r="2.67" />
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-responsive {
display: inline-block;
width: 47.5px;
height: 49px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: #34495e;
}
}
</style>
+40
View File
@@ -0,0 +1,40 @@
<template>
<svg class="va-icon-rich" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56.99 55">
<defs />
<title>overview_icon_6</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<rect class="cls-1" x="10.31" y="30.5" width="37.33" height="23" />
<path
class="cls-2"
d="M57,41.18l-7.85-16V24H8.81v1.11L0,41.11l2.63,1.45L8.81,31.33V55H49.15V32L54.3,42.5ZM46.15,52H11.81V27H46.15Z"
/>
<polygon class="cls-2" points="35.3 1.8 32.9 0 28.12 6.39 26.16 4.63 24.16 6.87 28.56 10.8 35.3 1.8" />
<polygon
class="cls-2"
points="22.3 12.46 19.9 10.67 15.12 17.05 13.16 15.3 11.16 17.54 15.56 21.47 22.3 12.46"
/>
<polygon
class="cls-2"
points="38.89 21.14 45.64 12.13 43.23 10.33 38.45 16.72 36.49 14.97 34.49 17.2 38.89 21.14"
/>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-rich {
display: inline-block;
width: 57px;
height: 55px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: #34495e;
}
}
</style>
+33
View File
@@ -0,0 +1,33 @@
<template>
<svg
xmlns:xlink="http://www.w3.org/1999/xlink"
class="va-icon-slower"
viewBox="0 0 24 24"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<!-- Generator: sketchtool 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>67046716-A590-445C-AC65-1EEF69089C00</title>
<defs />
<g id="symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon-slower" fill="#34495E">
<g>
<path
id="A"
d="M16.82,18.87 L16.028,16.17 L12.014,16.17 L11.24,18.87 L8,18.87 L12.374,6 L15.686,6 L20.06,18.87 L16.82,18.87 Z M14.048,8.934 L14.012,8.934 C14.012,8.934 13.724,10.338 13.49,11.148 L12.752,13.65 L15.29,13.65 L14.57,11.148 C14.336,10.338 14.048,8.934 14.048,8.934 Z"
/>
<rect id="Rectangle-4" x="5" y="11" width="2" height="2" rx="1" />
<rect id="Rectangle-4-Copy" x="6" y="7" width="3" height="2" rx="1" />
<rect id="Rectangle-4" x="4" y="15" width="2" height="2" rx="1" />
</g>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-slower {
width: 24px;
height: 24px;
}
</style>
+34
View File
@@ -0,0 +1,34 @@
<template>
<svg class="va-icon-vue" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.05 47.8">
<defs />
<title>overview_icon_1</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<polygon
class="cls-1"
points="27.75 21.19 18.48 6.18 4.78 6.18 27.75 42.92 50.89 6.18 36.13 6.18 27.75 21.19"
/>
<path
class="cls-2"
d="M33.08,0,27.44,9.76,21.84,0H0L27.43,47.8,55,0ZM27.43,15.77,34.81,3h4.6l-12,20.72L15.55,3h4.55ZM5.18,3h6.91L27.43,29.73,42.88,3h7L27.44,41.78Z"
/>
</g>
</g>
</svg>
</template>
<style lang="scss">
.va-icon-vue {
display: inline-block;
width: 55px;
height: 47.8px;
.cls-1 {
fill: #4ae387;
}
.cls-2 {
fill: #34495e;
}
}
</style>
+47
View File
@@ -0,0 +1,47 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" class="va-icon-vuestic" width="304" height="31" viewBox="0 0 304 31">
<defs>
<linearGradient :id="'ORIGINAL'" x1="0%" y1="50%" y2="50%">
<stop offset="0%" stop-color="#4AE387" />
<stop offset="100%" stop-color="#C8EA13" />
</linearGradient>
<linearGradient :id="'CORPORATE'" x1="0%" y1="50%" y2="50%">
<stop offset="0%" stop-color="#74BBFF" />
<stop offset="100%" stop-color="#6E85E8" />
</linearGradient>
</defs>
<g fill="none" fill-rule="evenodd" transform="scale(2)">
<path
:fill="`url(#${themeGradientId})`"
fill-rule="nonzero"
d="M6.36,15.36 L-7.10542736e-15,0.48 L3.672,0.48 L6.576,7.416 L9.48,0.48 L13.152,0.48 L6.792,15.36 L6.36,15.36 Z M21.744,10.032 L21.744,0.48 L25.248,0.48 L25.248,9.504 C25.248,11.424 26.16,12.096 27.384,12.096 C28.728,12.096 29.568,11.424 29.568,9.504 L29.568,0.48 L33.096,0.48 L33.096,10.032 C33.096,13.44 30.552,15.36 27.384,15.36 C24.096,15.36 21.744,13.344 21.744,10.032 Z M42.6,14.88 L42.6,0.48 L52.128,0.48 L52.128,3.6 L46.128,3.6 L46.128,6.192 L51.672,6.192 L51.672,9.264 L46.128,9.264 L46.128,11.856 L52.392,11.856 L52.392,14.88 L42.6,14.88 Z M61.224,10.656 L64.656,10.656 C64.656,11.496 65.328,12.168 66.144,12.168 C67.032,12.168 67.584,11.664 67.584,10.92 C67.584,9.84 66.168,9.528 64.992,9.12 C62.568,8.256 61.224,7.128 61.224,4.656 C61.224,2.112 63.408,7.10542736e-15 66.12,7.10542736e-15 C69.312,7.10542736e-15 70.824,2.04 71.016,4.704 L67.704,4.704 C67.704,3.888 67.2,3.216 66.216,3.216 C65.448,3.216 64.704,3.672 64.704,4.608 C64.704,5.688 66.024,5.88 67.248,6.24 C69.816,7.008 71.016,8.448 71.016,10.704 C71.016,13.248 68.856,15.36 66.144,15.36 C63,15.36 61.224,13.248 61.224,10.656 Z M82.896,14.88 L82.896,3.6 L79.68,3.6 L79.68,0.48 L89.712,0.48 L89.712,3.6 L86.448,3.6 L86.448,14.88 L82.896,14.88 Z M98.544,14.88 L98.544,0.48 L102.072,0.48 L102.072,14.88 L98.544,14.88 Z M111.72,7.68 C111.72,3.384 114.96,7.10542736e-15 119.52,7.10542736e-15 C122.256,7.10542736e-15 124.152,1.032 125.688,2.64 L123.264,4.944 C122.256,3.96 120.96,3.336 119.52,3.336 C116.952,3.336 115.296,5.256 115.296,7.68 C115.296,10.104 116.952,12.024 119.52,12.024 C120.96,12.024 122.256,11.4 123.264,10.416 L125.616,12.72 C124.176,14.232 122.184,15.36 119.52,15.36 C114.96,15.36 111.72,11.976 111.72,7.68 Z"
/>
<path
:fill="textColor"
d="M139.712,7.88 L139.712,6.152 L138.44,6.152 C138.272,6.152 138.066,6.162 137.822,6.182 C137.578,6.202 137.36,6.224 137.168,6.248 C137.424,5.984 137.682,5.704 137.942,5.408 C138.202,5.112 138.436,4.808 138.644,4.496 C138.852,4.184 139.022,3.87 139.154,3.554 C139.286,3.238 139.352,2.928 139.352,2.624 C139.352,2.248 139.288,1.906 139.16,1.598 C139.032,1.29 138.852,1.028 138.62,0.812 C138.388,0.596 138.112,0.428 137.792,0.308 C137.472,0.188 137.12,0.128 136.736,0.128 C136.456,0.128 136.198,0.152 135.962,0.2 C135.726,0.248 135.502,0.322 135.29,0.422 C135.078,0.522 134.876,0.65 134.684,0.806 C134.492,0.962 134.296,1.144 134.096,1.352 L134.096,1.352 L135.2,2.456 C135.384,2.272 135.574,2.106 135.77,1.958 C135.966,1.81 136.192,1.736 136.448,1.736 C136.728,1.736 136.954,1.818 137.126,1.982 C137.298,2.146 137.384,2.4 137.384,2.744 C137.384,3 137.302,3.274 137.138,3.566 C136.974,3.858 136.748,4.166 136.46,4.49 C136.172,4.814 135.834,5.16 135.446,5.528 C135.058,5.896 134.64,6.288 134.192,6.704 L134.192,6.704 L134.192,7.88 L139.712,7.88 Z M142.56,8.024 C142.912,8.024 143.2,7.904 143.424,7.664 C143.648,7.424 143.76,7.128 143.76,6.776 C143.76,6.424 143.648,6.128 143.424,5.888 C143.2,5.648 142.912,5.528 142.56,5.528 C142.208,5.528 141.92,5.648 141.696,5.888 C141.472,6.128 141.36,6.424 141.36,6.776 C141.36,7.128 141.472,7.424 141.696,7.664 C141.92,7.904 142.208,8.024 142.56,8.024 Z M150.736,7.88 L150.736,6.224 L149.368,6.224 L149.368,0.272 L147.856,0.272 C147.568,0.456 147.272,0.604 146.968,0.716 C146.664,0.828 146.296,0.928 145.864,1.016 L145.864,1.016 L145.864,2.288 L147.304,2.288 L147.304,6.224 L145.672,6.224 L145.672,7.88 L150.736,7.88 Z"
/>
</g>
</svg>
</template>
<script>
export default {
name: 'VaIconVuestic',
inject: ['contextConfig'],
computed: {
themeGradientId() {
return this.contextConfig.invertedColor ? 'CORPORATE' : 'ORIGINAL'
},
textColor() {
return this.contextConfig.invertedColor ? '#6E85E8' : '#E4FF32'
},
},
}
</script>
<style lang="scss">
.va-icon-vuestic {
.st0 {
fill: #4ae387;
}
}
</style>
+279
View File
@@ -0,0 +1,279 @@
<template>
<div ref="mapRef" class="line-map" />
</template>
<script setup lang="ts">
import { ref, toRef, computed, onMounted, onBeforeUnmount, watch, shallowRef, onUpdated } from 'vue'
import * as am5 from '@amcharts/amcharts5'
import * as am5map from '@amcharts/amcharts5/map'
import am5geodata_worldLow from '@amcharts/amcharts5-geodata/worldLow'
import am5themes_Animated from '@amcharts/amcharts5/themes/Animated'
import { useColors } from 'vuestic-ui'
import {
planeSVG,
useMapData,
CityItem,
getGeoBounds,
generateLineSeriesData,
compareStrings,
} from '../../data/maps/lineMapData'
const generateLabelText = (city?: string) => (city ? `Flights from ${city}` : '')
const generateButtonText = (city: string) => `Show flights from ${city}`
const props = withDefaults(
defineProps<{
mapData: CityItem[]
homeCity: string
modelValue: string
}>(),
{
homeCity: 'New York',
modelValue: 'New York',
},
)
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
}>()
const { colors } = useColors()
const mapRef = ref()
const mapRoot = shallowRef()
const mapChart = shallowRef()
const mapPolygonSeries = shallowRef()
const mapPointSeries = shallowRef()
const mapLineSeries = shallowRef()
const mapZoomControl = shallowRef()
const mapLabelText = shallowRef()
const mapLabelSvg = shallowRef()
const mapHomeCityButton = shallowRef()
const mainCity = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
},
})
const mapPointSeriesData = useMapData(toRef(props, 'mapData'))
const mapLineSeriesData = computed(() => {
const item = getItemByMainCityTitle()
return generateLineSeriesData(item)
})
const getItemByMainCityTitle = () =>
mapPointSeriesData.value.find(({ title }) => compareStrings(title, mainCity.value))
const zoomToGeoBounds = () => {
const item = getItemByMainCityTitle()
const geoBounds = getGeoBounds(item)
if (geoBounds) {
mapChart.value.zoomToGeoBounds(geoBounds)
}
}
const createMap = () => {
const root = am5.Root.new(mapRef.value)
root.setThemes([am5themes_Animated.new(root)])
const chart = root.container.children.push(
am5map.MapChart.new(root, {
minZoomLevel: 1,
maxZoomLevel: 10,
}),
)
const zoomControl = chart.set('zoomControl', am5map.ZoomControl.new(root, {}))
// polygon series
const polygonSeries = chart.series.push(
am5map.MapPolygonSeries.new(root, {
geoJSON: am5geodata_worldLow,
exclude: ['AQ'],
}),
)
polygonSeries.mapPolygons.template.setAll({
fill: am5.color(colors.secondary),
fillOpacity: 0.4,
strokeWidth: 0.5,
})
polygonSeries.events.on('datavalidated', zoomToGeoBounds)
// point series
const pointSeries = chart.series.push(
am5map.MapPointSeries.new(root, {
latitudeField: 'latitude',
longitudeField: 'longitude',
}),
)
// point series bullets
const bulletTemplate = am5.Template.new({}) as am5.Template<am5.Graphics>
bulletTemplate.events.on('click', (ev) => {
mainCity.value = (ev as any).target._dataItem.dataContext.title
})
pointSeries.bullets.push((root, series, dataItem) => {
const itemData = dataItem.dataContext as CityItem
const isMainCity = compareStrings(itemData.title, mainCity.value)
return am5.Bullet.new(root, {
sprite: am5.Graphics.new(
root,
{
svgPath: itemData.svgPath,
x: am5.percent(50),
y: am5.percent(50),
centerX: am5.percent(50),
centerY: am5.percent(50),
fill: am5.color(isMainCity ? colors.primary : itemData.color),
scale: isMainCity ? 1.5 : 1,
tooltipText: '{title}',
},
bulletTemplate,
),
})
})
// line series
const lineSeries = chart.series.push(am5map.MapLineSeries.new(root, {}))
lineSeries.mapLines.template.setAll({
stroke: am5.color(colors.primary),
strokeWidth: 2,
strokeOpacity: 0.5,
})
// set map data
pointSeries.data.setAll(mapPointSeriesData.value)
lineSeries.data.setAll(mapLineSeriesData.value as unknown[])
// label 'Flights from ...'
const labelContainer = chart.children.push(
am5.Container.new(root, {
x: 15,
y: 15,
layout: root.horizontalLayout,
}),
)
const labelSvg = labelContainer.children.push(
am5.Graphics.new(root, {
svgPath: planeSVG,
fill: am5.color(colors.info),
}),
)
const labelText = labelContainer.children.push(
am5.Label.new(root, {
text: generateLabelText(getItemByMainCityTitle()?.title),
fill: am5.color(colors.info),
fontSize: 22,
lineHeight: am5.p100,
}),
)
// button 'Show flights from homeCity'
const homeCityButton = chart.children.push(
am5.Button.new(root, {
x: 15,
y: 45,
label: am5.Label.new(root, {
text: generateButtonText(props.homeCity),
paddingTop: 0,
marginRight: 0,
paddingBottom: 0,
marginLeft: 0,
}),
visible: false,
}),
)
homeCityButton.events.on('click', () => {
mainCity.value = props.homeCity
homeCityButton.hide()
})
// assign objects to refs
mapRoot.value = root
mapChart.value = chart
mapZoomControl.value = zoomControl
mapPointSeries.value = pointSeries
mapLineSeries.value = lineSeries
mapPolygonSeries.value = polygonSeries
mapLabelSvg.value = labelSvg
mapLabelText.value = labelText
mapHomeCityButton.value = homeCityButton
}
const setPointSeriesData = () => {
mapPointSeries.value.data.setAll(mapPointSeriesData.value)
}
const setLineSeriesData = () => {
mapLineSeries.value.data.setAll(mapLineSeriesData.value ?? [])
}
const updateInteractiveElements = () => {
mapLabelText.value.set('text', generateLabelText(getItemByMainCityTitle()?.title))
mapHomeCityButton.value[compareStrings(props.homeCity, mainCity.value) ? 'hide' : 'show']?.()
}
const updateChartDataOnChangeTheme = () => {
if (mapRoot.value) {
mapPolygonSeries.value.mapPolygons.template.setAll({
fill: am5.color(colors.secondary),
})
mapLineSeries.value.mapLines.template.setAll({
stroke: am5.color(colors.primary),
})
mapLabelText.value.set('fill', am5.color(colors.info))
mapLabelSvg.value.set('fill', am5.color(colors.info))
setPointSeriesData()
}
}
const updateChartDataOnUpdateProps = () => {
if (mapRoot.value) {
setPointSeriesData()
setLineSeriesData()
updateInteractiveElements()
zoomToGeoBounds()
}
}
const disposeMap = () => {
if (mapRoot.value) {
mapRoot.value.dispose()
}
}
onMounted(createMap)
onUpdated(updateChartDataOnUpdateProps)
watch(colors, updateChartDataOnChangeTheme)
onBeforeUnmount(disposeMap)
</script>
<style lang="scss" scoped>
.line-map {
border-radius: inherit;
:deep(div),
:deep(canvas) {
border-radius: inherit;
}
}
</style>
+114
View File
@@ -0,0 +1,114 @@
<template>
<va-navbar class="app-layout-navbar">
<template #left>
<div class="left">
<va-icon-menu-collapsed
:class="{ 'x-flip': isSidebarMinimized }"
class="va-navbar__item"
:color="colors.primary"
@click="isSidebarMinimized = !isSidebarMinimized"
/>
<router-link to="/">
<vuestic-logo class="logo" />
</router-link>
</div>
</template>
<template #center>
<div class="app-navbar-center">
<span class="app-navbar-center__text mr-2">{{ t('navbar.messageUs') }}</span>
<a
class="app-navbar-center__mail mr-2"
href="mailto:hello@epicmax.co"
target="_blank"
:style="{ color: colors.primary }"
>
hello@epicmax.co
</a>
<va-button
href="https://github.com/epicmaxco/vuestic-admin"
color="#000000"
class="app-navbar-center__github-button"
icon="github"
target="_blank"
>
{{ t('navbar.repository') }}
</va-button>
</div>
</template>
<template #right>
<app-navbar-actions class="app-navbar__actions md5 lg4" :user-name="userName" />
</template>
</va-navbar>
</template>
<script setup>
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useGlobalStore } from '../../stores/global-store'
import { useI18n } from 'vue-i18n'
import { useColors } from 'vuestic-ui'
import VuesticLogo from '../VuesticLogo.vue'
import VaIconMenuCollapsed from '../icons/VaIconMenuCollapsed.vue'
import AppNavbarActions from './components/AppNavbarActions.vue'
const GlobalStore = useGlobalStore()
const { t } = useI18n()
const { isSidebarMinimized, userName } = storeToRefs(GlobalStore)
const { getColors } = useColors()
const colors = computed(() => getColors())
</script>
<style lang="scss" scoped>
.va-navbar {
box-shadow: var(--va-box-shadow);
z-index: 2;
@media screen and (max-width: 950px) {
.left {
width: 100%;
}
.app-navbar__actions {
width: 100%;
display: flex;
justify-content: space-between;
}
}
}
.left {
display: flex;
align-items: center;
& > * {
margin-right: 1.5rem;
}
& > *:last-child {
margin-right: 0;
}
}
.x-flip {
transform: scaleX(-100%);
}
.app-navbar-center {
display: flex;
align-items: center;
@media screen and (max-width: 1200px) {
&__github-button {
display: none;
}
}
@media screen and (max-width: 950px) {
&__text {
display: none;
}
}
}
</style>
@@ -0,0 +1,97 @@
<template>
<div class="app-navbar-actions">
<color-dropdown class="app-navbar-actions__item" />
<message-dropdown class="app-navbar-actions__item" />
<notification-dropdown class="app-navbar-actions__item" />
<!-- <settings-dropdown class="app-navbar-actions__item" /> -->
<language-dropdown class="app-navbar-actions__item" />
<profile-dropdown class="app-navbar-actions__item app-navbar-actions__item--profile">
<span>{{ userName }}</span>
</profile-dropdown>
</div>
</template>
<script setup lang="ts">
import LanguageDropdown from './dropdowns/LanguageDropdown.vue'
import ProfileDropdown from './dropdowns/ProfileDropdown.vue'
import NotificationDropdown from './dropdowns/NotificationDropdown.vue'
import MessageDropdown from './dropdowns/MessageDropdown.vue'
import ColorDropdown from './dropdowns/ColorDropdown.vue'
withDefaults(
defineProps<{
userName?: string
isTopBar?: boolean
}>(),
{
userName: '',
isTopBar: false,
},
)
defineEmits<{
(e: 'update:isTopBar', isTopBar: boolean): void
}>()
// const isTopBarProxy = computed({
// get() {
// return props.isTopBar
// },
//
// set(isTopBar: boolean) {
// emit('update:isTopBar', isTopBar)
// },
// })
</script>
<style lang="scss">
.app-navbar-actions {
display: flex;
align-items: center;
.va-dropdown__anchor {
color: var(--va-primary);
fill: var(--va-primary);
}
&__item {
padding: 0;
margin-left: 1.25rem;
margin-right: 1.25rem;
svg {
height: 24px;
}
&:last-of-type {
margin-right: 0;
}
&--profile {
display: flex;
justify-content: center;
margin: auto 0 auto 1.25rem;
}
.va-dropdown-content {
background-color: var(--va-white);
}
@media screen and (max-width: 640px) {
margin-right: 0;
&:first-of-type {
margin-left: 0;
}
&--profile {
position: absolute;
right: 0.75rem;
top: 1.25rem;
height: fit-content;
margin: auto;
}
}
}
}
</style>
@@ -0,0 +1,80 @@
<template>
<va-dropdown class="color-dropdown" :offset="[13, 0]" position="bottom">
<template #anchor>
<va-icon-color />
</template>
<va-dropdown-content class="color-dropdown__content pl-4 pr-4 pt-2 pb-2">
<va-button-toggle
v-model="currentTheme"
class="color-dropdown__toggle"
:options="themeOptions"
outline
round
grow
size="small"
/>
<table style="width: 100%">
<color-dropdown-item
v-for="colorName in colorNames"
:key="colorName"
class="color-picker-dropdown"
:color-name="colorName"
/>
</table>
</va-dropdown-content>
</va-dropdown>
</template>
<script setup lang="ts">
import VaIconColor from '../../../icons/VaIconColor.vue'
import ColorDropdownItem from './ColorDropdownItem.vue'
import { useColors } from 'vuestic-ui'
import { ref, watchEffect } from 'vue'
const { presets, applyPreset, colors } = useColors()
const currentTheme = ref('light')
watchEffect(() => {
applyPreset(currentTheme.value)
})
const themeOptions = Object.keys(presets.value).map((themeName) => ({
value: themeName,
label: themeName,
}))
const colorNames = Object.keys(colors)
</script>
<style lang="scss" scoped>
.color-dropdown {
cursor: pointer;
&__icon {
position: relative;
display: flex;
align-items: center;
}
.va-dropdown__anchor {
display: inline-block;
}
&__toggle {
width: 100%;
display: flex;
justify-content: stretch;
}
}
.button-restore {
display: flex;
margin: 0.375rem auto;
}
table {
margin: 1rem 0;
}
</style>
@@ -0,0 +1,44 @@
<template>
<tr>
<td class="color">
<va-color-indicator :color="theme[colorName]" />
</td>
<td>
{{ colorName }}
</td>
</tr>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useColors } from 'vuestic-ui'
const { getColors } = useColors()
defineProps<{
colorName: string
}>()
const theme = computed(() => {
return getColors()
})
</script>
<style lang="scss" scoped>
.color {
width: 16px;
height: 16px;
&-bordered {
border: 1px solid var(--va-gray);
border-radius: 50%;
}
}
td {
color: var(--va-gray);
padding: 0.25rem 0.5rem;
box-sizing: border-box;
vertical-align: middle;
&:first-child {
padding-right: 0;
}
}
</style>
@@ -0,0 +1,106 @@
<template>
<va-dropdown class="language-dropdown" fixed position="bottom" :offset="[13, 0]">
<template #anchor>
<va-icon :name="getFlagIcon(locale, 'large')" />
</template>
<va-dropdown-content class="language-dropdown__content pl-4 pr-4 pt-2 pb-2">
<div
v-for="(option, id) in options"
:key="id"
class="language-dropdown__item row align--center pt-1 pb-1 mt-2 mb-2"
:class="{ active: option.code === locale }"
@click="locale = option.code"
>
<va-icon :name="getFlagIcon(option.code, 'small')" />
<span class="dropdown-item__text">
{{ t(`language.${option.name}`) }}
</span>
</div>
</va-dropdown-content>
</va-dropdown>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t, locale } = useI18n()
withDefaults(
defineProps<{
options?: { code: string; name: string }[]
}>(),
{
options: () => [
{
code: 'gb',
name: 'english',
},
{
code: 'es',
name: 'spanish',
},
{
code: 'br',
name: 'brazilian_portuguese',
},
{
code: 'cn',
name: 'simplified_chinese',
},
{
code: 'ir',
name: 'persian',
},
],
},
)
function getFlagIcon(code: string, size: string) {
return `flag-icon-${code} ${size}`
}
</script>
<style lang="scss" scoped>
@import 'flag-icons/css/flag-icons.css';
.language-dropdown {
cursor: pointer;
&__content {
.fi-size-small {
min-width: 1.5rem;
min-height: 1.5rem;
margin-right: 0.5rem;
}
}
&__item {
padding-bottom: 0.625rem;
cursor: pointer;
flex-wrap: nowrap;
&:last-of-type {
padding-bottom: 0 !important;
}
&:hover {
color: var(--va-primary);
}
}
.fi::before {
content: '';
}
.fi-size-large {
display: block;
width: 32px;
height: 24px;
}
.va-dropdown__anchor {
display: inline-block;
}
}
</style>
@@ -0,0 +1,157 @@
<template>
<va-dropdown class="message-dropdown" position="bottom" :offset="[13, 0]">
<template #anchor>
<va-icon-message class="message-dropdown__icon" :class="{ 'message-dropdown__icon--unread': !allRead }" />
</template>
<va-dropdown-content class="message-dropdown__content pl-4 pr-4 pt-2 pb-2">
<div
v-for="message in messagesProxy"
:key="message.id"
class="message-dropdown__item row pt-1 pb-1 mt-2 mb-2"
:class="{ 'message-dropdown__item--unread': message.unread }"
@click="message.unread = false"
>
<img :src="message.details.avatar" class="message-dropdown__item__avatar mr-2" />
<span class="ellipsis" style="max-width: 85%">{{
t(`messages.${message.name}`, { name: message.details.name })
}}</span>
</div>
<div class="row justify--space-between mt-1">
<va-button class="md6 mr-2" size="small">{{ t('messages.all') }}</va-button>
<va-button
class="md6"
size="small"
preset="outline"
border-color="primary"
:disabled="allRead"
@click="markAllAsRead"
>{{ t('messages.mark_as_read') }}</va-button
>
</div>
</va-dropdown-content>
</va-dropdown>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import VaIconMessage from '../../../icons/VaIconMessage.vue'
const { t } = useI18n()
interface IMessage {
name: string
details: {
name: string
avatar: string
}
unread: boolean
id: number
}
const props = withDefaults(
defineProps<{
messages?: IMessage[]
}>(),
{
messages: () => [
{
name: 'new',
details: {
name: 'Oleg M',
avatar: 'https://picsum.photos/24?image=1083',
},
unread: true,
id: 1,
},
{
name: 'new',
details: {
name: 'Andrei H',
avatar: 'https://picsum.photos/24?image=1025',
},
unread: true,
id: 2,
},
],
},
)
const messagesProxy = ref<IMessage[]>([...props.messages])
const allRead = computed(() => {
return messagesProxy.value.every((message) => !message.unread)
})
function markAllAsRead() {
messagesProxy.value = messagesProxy.value.map((message) => ({
...message,
unread: false,
}))
}
</script>
<style lang="scss" scoped>
.message-dropdown {
cursor: pointer;
.message-dropdown__icon {
position: relative;
display: flex;
align-items: center;
&--unread::before {
content: '';
position: absolute;
right: 0;
left: 0;
top: -0.5rem;
background-color: var(--va-danger);
height: 0.375rem;
width: 0.375rem;
margin: 0 auto;
border-radius: 0.187rem;
}
}
&__item {
cursor: pointer;
margin-bottom: 0.75rem;
position: relative;
flex-wrap: nowrap;
&--unread {
color: var(--va-gray);
&::after {
content: '';
position: absolute;
right: 0;
top: 0;
bottom: 0;
height: 0.375rem;
width: 0.375rem;
background-color: var(--va-danger);
margin: auto;
border-radius: 0.187rem;
}
}
&:hover {
color: var(--va-primary);
}
&__avatar {
border-radius: 50%;
min-width: 1.5rem;
height: 1.5rem;
}
}
.va-dropdown__anchor {
display: inline-block;
}
}
</style>
@@ -0,0 +1,176 @@
<template>
<va-dropdown class="notification-dropdown" position="bottom" :offset="[13, 0]">
<template #anchor>
<va-icon-notification
class="notification-dropdown__icon"
:class="{ 'notification-dropdown__icon--unread': !allRead }"
/>
</template>
<va-dropdown-content class="notification-dropdown__content pl-3 pr-3 pt-2 pb-2">
<div
v-for="notification in notificationsProxy"
:key="notification.id"
class="notification-dropdown__item row pt-1 pb-1 mt-2 mb-2"
:class="{ 'notification-dropdown__item--unread': notification.unread }"
@click="notification.unread = false"
>
<img
v-if="notification.details.avatar"
class="mr-2 notification-dropdown__item__avatar"
:src="notification.details.avatar"
/>
<span class="ellipsis" style="max-width: 85%">
<span v-if="notification.details.name" class="text--bold">{{ notification.details.name }}</span>
{{ t(`notifications.${notification.name}`, { type: notification.details.type }) }}
</span>
</div>
<div class="row justify--space-between mt-1">
<va-button class="md6 mr-2" size="small">{{ t('notifications.all') }}</va-button>
<va-button
class="md6"
size="small"
preset="outline"
border-color="primary"
:disabled="allRead"
@click="markAllAsRead"
>{{ t('notifications.mark_as_read') }}</va-button
>
</div>
</va-dropdown-content>
</va-dropdown>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import VaIconNotification from '../../../icons/VaIconNotification.vue'
const { t } = useI18n()
interface INotification {
name: string
details: {
name: string
avatar: string
type?: string
}
unread: boolean
id: number
}
const props = withDefaults(
defineProps<{
notifications?: INotification[]
}>(),
{
notifications: () => [
{
name: 'sentMessage',
details: { name: 'Vasily S', avatar: 'https://picsum.photos/123' },
unread: true,
id: 1,
},
{
name: 'uploadedZip',
details: {
name: 'Oleg M',
avatar: 'https://picsum.photos/100',
type: 'typography component',
},
unread: true,
id: 2,
},
{
name: 'startedTopic',
details: { name: 'Andrei H', avatar: 'https://picsum.photos/24' },
unread: true,
id: 3,
},
],
},
)
const notificationsProxy = ref<INotification[]>([...props.notifications])
const allRead = computed(() => {
return notificationsProxy.value.every((notification) => !notification.unread)
})
function markAllAsRead() {
notificationsProxy.value = notificationsProxy.value.map((notification) => ({
...notification,
unread: false,
}))
}
</script>
<style lang="scss" scoped>
.row {
flex-wrap: nowrap !important;
}
.md6 {
box-sizing: border-box;
}
.notification-dropdown {
cursor: pointer;
.notification-dropdown__icon {
position: relative;
display: flex;
align-items: center;
&--unread::before {
content: '';
position: absolute;
right: 0;
left: 0;
top: -0.5rem;
// background-color: $brand-danger;
height: 0.375rem;
width: 0.375rem;
margin: 0 auto;
border-radius: 0.187rem;
}
}
&__content {
max-width: 20rem;
}
&__item {
cursor: pointer;
margin-bottom: 0.75rem;
flex-wrap: nowrap;
position: relative;
&--unread {
&::after {
content: '';
position: absolute;
right: 0;
top: 0;
bottom: 0;
height: 0.375rem;
width: 0.375rem;
margin: auto;
border-radius: 0.187rem;
}
}
&:hover {
color: var(--va-primary);
}
&__avatar {
border-radius: 50%;
width: 1.5rem;
height: 1.5rem;
min-width: 1.5rem;
}
}
.va-dropdown__anchor {
display: inline-block;
}
}
</style>
@@ -0,0 +1,68 @@
<template>
<div class="profile-dropdown-wrapper">
<va-dropdown v-model="isShown" class="profile-dropdown" stick-to-edges placement="bottom" :offset="[13, 0]">
<template #anchor>
<span class="profile-dropdown__anchor">
<slot />
<va-icon class="px-2" :name="isShown ? 'angle_up' : 'angle_down'" :color="colors.primary" />
</span>
</template>
<va-dropdown-content class="profile-dropdown__content">
<va-list-item v-for="option in options" :key="option.name" class="pa-2">
<router-link :to="{ name: option.redirectTo }" class="profile-dropdown__item">
{{ t(`user.${option.name}`) }}
</router-link>
</va-list-item>
</va-dropdown-content>
</va-dropdown>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useColors } from 'vuestic-ui'
const { t } = useI18n()
const { colors } = useColors()
withDefaults(
defineProps<{
options?: { name: string; redirectTo: string }[]
}>(),
{
options: () => [
{
name: 'profile',
redirectTo: '',
},
{
name: 'logout',
redirectTo: 'login',
},
],
},
)
const isShown = ref(false)
</script>
<style lang="scss" scoped>
.profile-dropdown {
cursor: pointer;
&__anchor {
display: inline-block;
}
&__item {
display: block;
color: var(--va-gray);
&:hover,
&:active {
color: var(--va-primary);
}
}
}
</style>
@@ -0,0 +1,83 @@
<template>
<va-dropdown class="settings-dropdown" position="bottom" boundary-body :offset="[13, 0]">
<template #anchor>
<va-icon
name="vuestic-iconset-settings"
style="font-size: 1.4rem; display: flex"
class="settings-dropdown__icon"
/>
</template>
<va-dropdown-content class="settings-dropdown__content pl-4 pr-4 pt-2 pb-2">
<div class="settings-dropdown__content-label mt-2 mb-3" :style="{ color: colors.primary }">
{{ t('dashboard.navigationLayout') }}
</div>
<va-button-toggle
v-model="isTopBarProxy"
outline
:options="options"
class="settings-dropdown__control mb-2"
size="small"
/>
</va-dropdown-content>
</va-dropdown>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useColors } from 'vuestic-ui'
const { colors } = useColors()
const { t } = useI18n()
const props = defineProps<{
isTopBar: boolean
}>()
const emit = defineEmits<{
(e: 'update:isTopBar', value: boolean): void
}>()
const options = ref([
{ label: t('dashboard.sideBarButton'), value: String(false) }, // NOTE: boolean is unsupported for va-dropdown
{ label: t('dashboard.topBarButton'), value: String(true) },
])
const isTopBarProxy = computed({
get() {
return String(props.isTopBar)
},
set() {
emit('update:isTopBar', props.isTopBar)
},
})
</script>
<style lang="scss">
.settings-dropdown {
cursor: pointer;
&__icon {
position: relative;
display: flex;
align-items: center;
}
&__content {
&-label {
margin-bottom: 0.5rem;
}
}
&__control {
.va-button-group {
margin: 0;
}
}
.va-dropdown__anchor {
display: inline-block;
}
}
</style>
+231
View File
@@ -0,0 +1,231 @@
export interface INavigationRoute {
name: string
displayName: string
meta: { icon: string }
children?: INavigationRoute[]
}
export default {
root: {
name: '/',
displayName: 'navigationRoutes.home',
},
routes: [
{
name: 'data-analysis',
displayName: 'Analysis',
meta: {
icon: 'vuestic-iconset-dashboard',
},
},
// {
// name: 'statistics',
// displayName: 'menu.statistics',
// meta: {
// icon: 'vuestic-iconset-statistics',
// },
// disabled: true,
// children: [
// {
// name: 'charts',
// displayName: 'menu.charts',
// },
// {
// name: 'progress-bars',
// displayName: 'menu.progressBars',
// },
// ],
// },
// {
// name: 'forms',
// displayName: 'menu.forms',
// meta: {
// icon: 'vuestic-iconset-forms',
// },
// disabled: true,
// children: [
// {
// name: 'form-elements',
// displayName: 'menu.formElements',
// },
// {
// name: 'medium-editor',
// displayName: 'menu.mediumEditor',
// },
// ],
// },
// {
// name: 'tables',
// displayName: 'menu.tables',
// meta: {
// icon: 'vuestic-iconset-tables',
// },
// children: [
// {
// name: 'markup',
// displayName: 'menu.markupTables',
// },
// // {
// // name: 'data',
// // displayName: 'menu.dataTables',
// // },
// ],
// },
// {
// name: 'ui',
// displayName: 'menu.uiElements',
// meta: {
// icon: 'vuestic-iconset-ui-elements',
// },
// disabled: true,
// children: [
// {
// name: 'buttons',
// displayName: 'menu.buttons',
// },
// {
// name: 'cards',
// displayName: 'menu.cards',
// },
// {
// name: 'chat',
// displayName: 'menu.chat',
// },
// {
// name: 'chips',
// displayName: 'menu.chips',
// },
// {
// name: 'collapses',
// displayName: 'menu.collapses',
// },
// {
// name: 'colors',
// displayName: 'menu.colors',
// },
// // {
// // name: 'color-pickers',
// // displayName: 'menu.colorPickers',
// // },
// {
// name: 'file-upload',
// displayName: 'menu.fileUpload',
// },
// {
// name: 'grid',
// displayName: 'menu.grid',
// },
// {
// name: 'icon-sets',
// displayName: 'menu.icons',
// children: [
// {
// displayName: 'concrete',
// name: 'icon-set',
// },
// ],
// },
// {
// name: 'lists',
// displayName: 'menu.lists',
// },
// {
// name: 'modals',
// displayName: 'menu.modals',
// },
// {
// name: 'notifications',
// displayName: 'menu.notifications',
// },
// {
// name: 'popovers',
// displayName: 'menu.popovers',
// },
// {
// name: 'rating',
// displayName: 'menu.rating',
// },
// {
// name: 'sliders',
// displayName: 'menu.sliders',
// },
// {
// name: 'spacing',
// displayName: 'menu.spacing',
// },
// {
// name: 'spinners',
// displayName: 'menu.spinners',
// },
// {
// name: 'tabs',
// displayName: 'menu.tabs',
// },
// // {
// // name: "timelines",
// // displayName: "menu.timelines",
// // },
// {
// name: 'tree-view',
// displayName: 'menu.treeView',
// },
// {
// name: 'typography',
// displayName: 'menu.typography',
// },
// ],
// },
// {
// name: 'maps',
// displayName: 'menu.maps',
// meta: {
// icon: 'vuestic-iconset-maps',
// },
// disabled: true,
// children: [
// {
// name: 'maplibre-maps',
// displayName: 'menu.maplibre-maps',
// },
// {
// name: 'yandex-maps',
// displayName: 'menu.yandex-maps',
// },
// {
// name: 'leaflet-maps',
// displayName: 'menu.leaflet-maps',
// },
// {
// name: 'bubble-maps',
// displayName: 'menu.bubble-maps',
// },
// {
// name: 'line-maps',
// displayName: 'menu.line-maps',
// },
// ],
// },
// {
// name: 'pages',
// displayName: 'menu.pages',
// meta: {
// icon: 'vuestic-iconset-files',
// },
// disabled: true,
// children: [
// {
// name: 'login',
// displayName: 'menu.login-singup',
// },
// {
// name: '404-pages',
// displayName: 'menu.404-pages',
// },
// {
// name: 'faq',
// displayName: 'menu.faq',
// },
// ],
// },
] as INavigationRoute[],
}
+50
View File
@@ -0,0 +1,50 @@
<template>
<va-sidebar :width="width" :minimized="minimized" :minimized-width="minimizedWidth" :animated="animated">
<menu-minimized v-if="minimized" :items="items" />
<menu-accordion v-else :items="items" />
</va-sidebar>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import NavigationRoutes from './NavigationRoutes'
import MenuAccordion from './menu/MenuAccordion.vue'
import MenuMinimized from './menu/MenuMinimized.vue'
withDefaults(
defineProps<{
width?: string
color?: string
animated?: boolean
minimized?: boolean
minimizedWidth?: string
}>(),
{
width: '16rem',
color: 'secondary',
animated: true,
minimized: true,
minimizedWidth: undefined,
},
)
const items = ref(NavigationRoutes.routes)
</script>
<style lang="scss">
.va-sidebar {
&__menu {
padding: 2rem 0;
}
&-item {
&__icon {
width: 1.5rem;
height: 1.5rem;
display: flex;
justify-content: center;
align-items: center;
}
}
}
</style>
@@ -0,0 +1,74 @@
<template>
<va-accordion v-model="accordionValue" class="sidebar-accordion va-sidebar__menu__inner" multiple>
<va-collapse v-for="(route, idx) in items" :key="idx">
<template #header>
<va-sidebar-item :active="isRouteActive(route)" :to="route.children ? undefined : { name: route.name }">
<va-sidebar-item-content>
<va-icon :name="route.meta.icon" class="va-sidebar-item__icon" />
<va-sidebar-item-title>
{{ t(route.displayName) }}
</va-sidebar-item-title>
<va-icon v-if="route.children" :name="accordionValue[idx] ? 'expand_less' : 'expand_more'" />
</va-sidebar-item-content>
</va-sidebar-item>
</template>
<template v-for="(child, index) in route.children" :key="index">
<va-sidebar-item :active="isRouteActive(child)" :to="{ name: child.name }">
<va-sidebar-item-content>
<div class="va-sidebar-item__icon" />
<va-sidebar-item-title>
{{ t(child.displayName) }}
</va-sidebar-item-title>
</va-sidebar-item-content>
</va-sidebar-item>
</template>
</va-collapse>
</va-accordion>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { INavigationRoute } from '../NavigationRoutes'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = withDefaults(
defineProps<{
items?: INavigationRoute[]
}>(),
{
items: () => [],
},
)
const accordionValue = ref<boolean[]>([])
onMounted(() => {
accordionValue.value = props.items.map((item) => isItemExpanded(item))
})
// function isGroup(item: INavigationRoute) {
// return !!item.children
// }
function isRouteActive(item: INavigationRoute) {
return item.name === useRoute().name
}
function isItemExpanded(item: INavigationRoute): boolean {
if (!item.children) {
return false
}
const isCurrentItemActive = isRouteActive(item)
const isChildActive = !!item.children.find((child) =>
child.children ? isItemExpanded(child) : isRouteActive(child),
)
return isCurrentItemActive || isChildActive
}
</script>
@@ -0,0 +1,107 @@
<template>
<va-dropdown
v-for="(route, idx) in items"
:key="idx"
v-model="dropdownsValue[idx]"
placement="right-start"
prevent-overflow
:offset="[1, 0]"
>
<template #anchor>
<va-sidebar-item :active="isItemChildsActive(route)" :to="route.children ? undefined : { name: route.name }">
<va-sidebar-item-content>
<va-icon :name="route.meta.icon" class="va-sidebar-item__icon" />
<va-icon
v-if="route.children"
class="more_icon"
:name="dropdownsValue[idx] ? 'chevron_left' : 'chevron_right'"
/>
</va-sidebar-item-content>
</va-sidebar-item>
</template>
<div class="sidebar-item__children">
<template v-for="(child, index) in route.children" :key="index">
<va-sidebar-item :active="isRouteActive(child)" :to="{ name: child.name }">
<va-sidebar-item-content>
<va-sidebar-item-title>
{{ t(child.displayName) }}
</va-sidebar-item-title>
</va-sidebar-item-content>
</va-sidebar-item>
</template>
</div>
</va-dropdown>
</template>
<script setup lang="ts">
import { INavigationRoute } from '../NavigationRoutes'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
withDefaults(
defineProps<{
items?: INavigationRoute[]
}>(),
{
items: () => [],
},
)
const dropdownsValue = ref([])
// function isGroup(item: INavigationRoute) {
// return !!item.children
// }
function isRouteActive(item: INavigationRoute) {
return item.name === useRoute().name
}
function isItemChildsActive(item: INavigationRoute): boolean {
if (!item.children) {
return false
}
const isCurrentItemActive = isRouteActive(item)
let isChildActive = false
if (item.children) {
isChildActive = !!item.children.find((child) =>
child.children ? isItemChildsActive(child) : isRouteActive(child),
)
}
return isCurrentItemActive || isChildActive
}
</script>
<style lang="scss">
.sidebar-item {
&__children {
max-height: 60vh;
overflow-y: auto;
overflow-x: visible;
width: 16rem;
color: var(--va-gray);
background: var(--va-white, #fff);
box-shadow: var(--va-box-shadow);
}
}
.va-sidebar-item {
&-content {
position: relative;
.more_icon {
text-align: center;
position: absolute;
bottom: 0.5rem;
top: 50%;
right: 0;
transform: translateY(-50%);
}
}
}
</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 @@
export default {
name: 'Idle',
parameter: [], // 這個mode用到的參數
showParameter: [], // 有要秀給user看的參數
headerParameter: () => [], // export header的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {},
charts: {},
}
+94
View File
@@ -0,0 +1,94 @@
export default {
name: 'Cali Mode - test',
parameter: ['ADC_DAC_CHANNEL_15', 'ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'DAC_LEVEL_V_OUT_15', 'DAC_VOLT_BUTTON'], // 這個mode用到的參數
showParameter: ['ADC_DAC_CHANNEL_15', 'ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'DAC_LEVEL_V_OUT_15', 'DAC_VOLT_BUTTON'], // 有要秀給user看的參數
headerParameter: () => [
'ADC_DAC_CHANNEL_15',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'DAC_LEVEL_V_OUT_15',
'DAC_VOLT_BUTTON',
], // export header的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
m: 60 * 1e6,
h: 60 * 60 * 1e6,
auto: 1,
},
defaultUnit: 'us',
},
0: {
name: 'I_in',
unit: {
nA: 1,
},
defaultUnit: 'nA',
},
1: {
name: 'V_in/V_out',
unit: {
uV: 1,
},
defaultUnit: 'uV',
},
2: {
name: 'Gain',
unit: {
default: 1,
},
defaultUnit: 'default',
},
},
charts: {
default: [
{
name: 'Cali Mode - test',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'ADC',
x1: {
channel: 'time',
},
y1: {
channel: 0,
},
},
],
},
],
},
],
},
}
+87
View File
@@ -0,0 +1,87 @@
export default {
name: 'Cali DAC - test',
parameter: ['CTRL_HIGH_Z_15', 'DAC_VOLT'], // 這個mode用到的參數
showParameter: ['CTRL_HIGH_Z_15', 'DAC_VOLT'], // 有要秀給user看的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
m: 60 * 1e6,
h: 60 * 60 * 1e6,
auto: 1,
},
defaultUnit: 'us',
},
0: {
name: 'I_in',
unit: {
nA: 1,
},
defaultUnit: 'nA',
},
1: {
name: 'V_in/V_out',
unit: {
uV: 1,
},
defaultUnit: 'uV',
},
2: {
name: 'Gain',
unit: {
default: 1,
},
defaultUnit: 'default',
},
},
charts: {
default: [
{
name: 'Cali DAC - test',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'DAC',
x1: {
channel: 'time',
},
y1: {
channel: 0,
},
},
],
},
],
},
],
},
}
@@ -0,0 +1,97 @@
export default {
name: 'Chronoamperometry',
parameter: ['CA_VOLT', 'SAMPLE_RATE', 'ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'CTRL_HIGH_Z_15', 'TIME_DURATION'], // 這個mode用到的參數
showParameter: ['ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'CTRL_HIGH_Z_15', 'CA_VOLT', 'SAMPLE_RATE', 'TIME_DURATION'], // 有要秀給user看的參數
headerParameter: () => ['CA_VOLT', 'SAMPLE_RATE', 'ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'TIME_DURATION'], // 有要秀給user看的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'Potential',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_out',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
},
charts: {
default: [
{
name: 'Chronoamperometric',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'chrono',
x1: {
channel: 'time',
},
y1: {
channel: 0,
},
},
],
},
],
},
],
},
}
@@ -0,0 +1,167 @@
export default {
name: 'Chronopotentiometry',
parameter: [
'Charge',
'Const_Current_value',
'SAMPLE_RATE',
'VOLTSTOP_MAX',
'VOLTSTOP_MIN',
'CC_CP_SPEED',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'TIME_DURATION',
], // 這個mode用到的參數
showParameter: [
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'CTRL_HIGH_Z_15',
'SAMPLE_RATE',
'Charge',
'Const_Current_value',
'VOLTSTOP_MAX',
'VOLTSTOP_MIN',
'TIME_DURATION',
], // 有要秀給user看的參數
headerParameter: () => [
'Charge',
'Const_Current_value',
'SAMPLE_RATE',
'VOLTSTOP_MAX',
'VOLTSTOP_MIN',
'CC_CP_SPEED',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'TIME_DURATION',
], // 有要秀給user看的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'Potential',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_out',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
3: {
name: 'sum_cnt',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'uV',
},
4: {
name: 'sum_adc_delta_Iin',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'uV',
},
5: {
name: 'sum_adc_delta_Voutin',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'uV',
},
6: {
name: 'resis',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'uV',
},
},
charts: {
default: [
{
name: 'Chronopotentiometry',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'CP',
x1: {
channel: 'time',
},
y1: {
channel: 1,
},
},
],
},
],
},
],
},
}
@@ -0,0 +1,127 @@
export default {
name: 'Constant Current',
parameter: [
'Charge',
'Const_Current_value',
'SAMPLE_RATE',
'VOLTSTOP_MAX',
'VOLTSTOP_MIN',
'CC_CP_SPEED',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'TIME_DURATION',
], // 這個mode用到的參數
showParameter: [
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'CTRL_HIGH_Z_15',
'SAMPLE_RATE',
'Charge',
'Const_Current_value',
'VOLTSTOP_MAX',
'VOLTSTOP_MIN',
'TIME_DURATION',
], // 有要秀給user看的參數
headerParameter: () => [
'Charge',
'Const_Current_value',
'SAMPLE_RATE',
'VOLTSTOP_MAX',
'VOLTSTOP_MIN',
'CC_CP_SPEED',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'TIME_DURATION',
], // 有要秀給user看的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'V_in',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_out',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
},
charts: {
default: [
{
name: 'Constant Current',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'CC',
x1: {
channel: 'time',
},
y1: {
channel: 1,
},
},
],
},
],
},
],
},
}
+134
View File
@@ -0,0 +1,134 @@
export default {
name: 'Cycle I-V',
parameter: [
'VOLT_ORIGIN',
'VOLT_FINAL',
'VOLT_STEP',
'STEP_TIME',
'SAMPLE_RATE',
'CYCLE_NUMBER',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'CTRL_HIGH_Z_15',
], // 這個mode用到的參數
showParameter: [
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'CTRL_HIGH_Z_15',
'VOLT_ORIGIN',
'VOLT_FINAL',
'VOLT_STEP',
'STEP_TIME',
'SAMPLE_RATE',
'CYCLE_NUMBER',
], // 有要秀給user看的參數
headerParameter: () => [
'VOLT_ORIGIN',
'VOLT_FINAL',
'VOLT_STEP',
'STEP_TIME',
'SAMPLE_RATE',
'CYCLE_NUMBER',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
], // export header的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'V_out',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_in',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
3: {
name: 'Cycle number',
unit: {
cycle: 1,
},
defaultUnit: 'cycle',
downloadUnit: null,
},
},
charts: {
default: [
{
name: 'Cycle I-V',
description: '',
subplot: [
{
x1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'CycleIV',
x1: {
channel: 1,
},
y1: {
channel: 0,
},
},
],
},
],
},
],
},
}
@@ -0,0 +1,134 @@
export default {
name: 'Cyclic Voltammetry',
parameter: [
'VOLT_INITIAL',
'VOLT_MAX',
'VOLT_MIN',
'Scan_Rate',
'SAMPLE_RATE',
'CYCLE_NUMBER',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'CTRL_HIGH_Z_15',
], // 這個mode用到的參數
showParameter: [
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'CTRL_HIGH_Z_15',
'VOLT_INITIAL',
'VOLT_MAX',
'VOLT_MIN',
'Scan_Rate',
'SAMPLE_RATE',
'CYCLE_NUMBER',
], // 有要秀給user看的參數
headerParameter: () => [
'VOLT_INITIAL',
'VOLT_MAX',
'VOLT_MIN',
'Scan_Rate',
'SAMPLE_RATE',
'CYCLE_NUMBER',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
], // 有要秀給user看的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
m: 60 * 1e6,
h: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'Potential',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_out',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
3: {
name: 'Cycle number',
unit: {
cycle: 1,
},
defaultUnit: 'cycle',
downloadUnit: null,
},
},
charts: {
default: [
{
name: 'Cyclic Voltammetry',
description: '',
subplot: [
{
x1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'CV',
x1: {
channel: 1,
},
y1: {
channel: 0,
},
},
],
},
],
},
],
},
}
+135
View File
@@ -0,0 +1,135 @@
export default {
name: 'Differential Pulse Voltammetry',
parameter: ['DPV_e_init', 'DPV_e_final', 'DPV_increment', 'DPV_amp', 'DPV_pul_width', 'DPV_step_time'], // 這個mode用到的參數
parameterInMode: {
0: ['DPV_e_init', 'DPV_e_final', 'DPV_increment', 'DPV_amp', 'DPV_pul_width', 'DPV_step_time'],
1: [
'DPV_e_init',
'DPV_e_1',
'DPV_e_2',
'DPV_e_final',
'DPV_increment',
'DPV_amp',
'DPV_pul_width',
'DPV_step_time',
'CYCLE_NUMBER',
'DPV_pulse_option',
'DPV_curr_rec',
],
2: [
'DPV_e_init',
'DPV_e_final',
'DPV_increment',
'DPV_amp',
'DPV_pul_width',
'DPV_step_time',
'CYCLE_NUMBER',
'DPV_notify_rate',
'DPV_engineering_enable',
'DPV_e_1',
'DPV_e_2',
'DPV_pulse_option',
],
}, // 這個mode用到的參數
showParameter: [], // 有要秀給user看的參數
headerParameter: () => [], // export header的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'Potential',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_out',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
3: {
name: 'Cycle number',
unit: {
cycle: 1,
},
defaultUnit: 'cycle',
downloadUnit: null,
},
},
charts: {
default: [
{
name: 'Open Circuit Potential',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'OCP',
x1: {
channel: 'time',
},
y1: {
channel: 1,
},
},
],
},
],
},
],
},
}
+93
View File
@@ -0,0 +1,93 @@
export default {
name: 'Dev Mode',
parameter: ['ADC_VALUE_I'],
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
m: 60 * 1e6,
h: 60 * 60 * 1e6,
auto: 1,
},
defaultUnit: 'auto',
},
0: {
name: '0',
unit: {
default: 1,
},
defaultUnit: 'default',
},
1: {
name: '1',
unit: {
default: 1,
},
defaultUnit: 'default',
},
2: {
name: '2',
unit: {
default: 1,
},
defaultUnit: 'default',
},
3: {
name: '3',
unit: {
default: 1,
},
defaultUnit: 'default',
},
},
charts: {
default: [
{
name: 'Dev Mode',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'dev',
x1: {
channel: 'time',
},
y1: {
channel: 1,
},
},
],
},
],
},
],
},
}
@@ -0,0 +1,97 @@
export default {
name: 'Function Generator',
parameter: ['DAC_VOLT', 'SAMPLE_RATE', 'ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'CTRL_HIGH_Z_15', 'TIME_DURATION'], // 這個mode用到的參數
showParameter: ['ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'CTRL_HIGH_Z_15', 'DAC_VOLT', 'SAMPLE_RATE', 'TIME_DURATION'], // 有要秀給user看的參數
headerParameter: () => ['DAC_VOLT', 'SAMPLE_RATE', 'ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'TIME_DURATION'], // export header的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'V_out',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_in',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
},
charts: {
default: [
{
name: 'Function Generator',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'FuncGen',
x1: {
channel: 'time',
},
y1: {
channel: 1,
},
},
],
},
],
},
],
},
}
+97
View File
@@ -0,0 +1,97 @@
export default {
name: 'I-T Graph',
parameter: ['DAC_VOLT', 'SAMPLE_RATE', 'ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'CTRL_HIGH_Z_15', 'TIME_DURATION'], // 這個mode用到的參數
showParameter: ['ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'CTRL_HIGH_Z_15', 'DAC_VOLT', 'SAMPLE_RATE', 'TIME_DURATION'], // 有要秀給user看的參數
headerParameter: () => ['DAC_VOLT', 'SAMPLE_RATE', 'ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'TIME_DURATION'], // export header的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'V_in',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_out',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
},
charts: {
default: [
{
name: 'I-T Graph',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'IT',
x1: {
channel: 'time',
},
y1: {
channel: 0,
},
},
],
},
],
},
],
},
}
+123
View File
@@ -0,0 +1,123 @@
export default {
name: 'I-V Curve',
parameter: [
'VOLT_ORIGIN',
'VOLT_FINAL',
'VOLT_STEP',
'STEP_TIME',
'SAMPLE_RATE',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'CTRL_HIGH_Z_15',
], // 這個mode用到的參數
showParameter: [
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'CTRL_HIGH_Z_15',
'VOLT_ORIGIN',
'VOLT_FINAL',
'VOLT_STEP',
'STEP_TIME',
'SAMPLE_RATE',
], // 有要秀給user看的參數
headerParameter: () => [
'VOLT_ORIGIN',
'VOLT_FINAL',
'VOLT_STEP',
'STEP_TIME',
'SAMPLE_RATE',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
], // export header的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'V_out',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_in',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
},
charts: {
default: [
{
name: 'I-V Curve',
description: '',
subplot: [
{
x1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'IV',
x1: {
channel: 1,
},
y1: {
channel: 0,
},
},
],
},
],
},
],
},
}
@@ -0,0 +1,120 @@
export default {
name: 'Linear Sweep Voltammetry',
parameter: [
'VOLT_INITIAL',
'VOLT_EFINAL',
'Scan_Rate',
'SAMPLE_RATE',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'CTRL_HIGH_Z_15',
], // 這個mode用到的參數
showParameter: [
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
'CTRL_HIGH_Z_15',
'VOLT_INITIAL',
'VOLT_EFINAL',
'Scan_Rate',
'SAMPLE_RATE',
], // 有要秀給user看的參數
headerParameter: () => [
'VOLT_INITIAL',
'VOLT_EFINAL',
'Scan_Rate',
'SAMPLE_RATE',
'ADC_LEVEL_I_15',
'ADC_LEVEL_V_IN_15',
], // 有要秀給user看的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'Potential',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_out',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
},
charts: {
default: [
{
name: 'Linear Sweep Voltammetry',
description: '',
subplot: [
{
x1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'LSV',
x1: {
channel: 1,
},
y1: {
channel: 0,
},
},
],
},
],
},
],
},
}
+97
View File
@@ -0,0 +1,97 @@
export default {
name: 'Open Circuit Potential',
parameter: ['SAMPLE_RATE', 'ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'TIME_DURATION'], // 這個mode用到的參數
showParameter: ['ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'SAMPLE_RATE', 'TIME_DURATION'], // 有要秀給user看的參數
headerParameter: () => ['SAMPLE_RATE', 'ADC_LEVEL_I_15', 'ADC_LEVEL_V_IN_15', 'TIME_DURATION'], // export header的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'Potential',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
2: {
name: 'V_in',
unit: {
uV: 1,
mV: 1e3,
V: 1e6,
},
defaultUnit: 'mV',
downloadUnit: 'mV',
},
},
charts: {
default: [
{
name: 'Open Circuit Potential',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'OCP',
x1: {
channel: 'time',
},
y1: {
channel: 1,
},
},
],
},
],
},
],
},
}
@@ -0,0 +1,103 @@
export default {
name: 'Pulse Sensing',
parameter: ['V_initial', 't_pulse', 'CURR_REC'],
// parameter: ['V_initial', 't_pulse', 'CURR_REC_START', 'CURR_REC_END', 'segment_ui_order', 'segment_order', 'DATA_OUTPUT', 'CYCLE_NUMBER'], // 這個mode用到的參數
showParameter: ['V_initial', 't_pulse', 'CURR_REC'], // 有要秀給user看的參數
headerParameter: (parameterSet) => {
// [
// 'V_initial_0', 't_pulse_0', 'CURR_REC_START_0', 'CURR_REC_END_0',
// 'V_initial_1', 't_pulse_1', 'CURR_REC_START_1', 'CURR_REC_END_1',
// 'V_initial_2', 't_pulse_2', 'CURR_REC_START_2', 'CURR_REC_END_2',
// 'V_initial_3', 't_pulse_3', 'CURR_REC_START_3', 'CURR_REC_END_3',
// ]
const defaultParameter = ['V_initial', 't_pulse', 'CURR_REC']
const ret = []
for (let index = 0; index < 4; index++) {
if (parameterSet.segment_order[index] !== -1) {
ret.push(...defaultParameter.map((ele) => [ele, index]))
}
}
return ret
}, // export header的參數
valScales: {
linear: {
func: (val) => {
return val
},
},
log: {
func: (val) => {
return Math.log10(Math.abs(val))
},
},
},
channels: {
time: {
name: 'Time',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
minute: 60 * 1e6,
hour: 60 * 60 * 1e6,
},
defaultUnit: 'ms',
downloadUnit: 'ms',
},
0: {
name: 'I_in_pul1',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
1: {
name: 'I_in_pul2',
unit: {
nA: 1,
uA: 1e3,
mA: 1e6,
},
defaultUnit: 'mA',
downloadUnit: 'mA',
},
},
charts: {
default: [
{
name: 'Pulse Sensing',
description: '',
subplot: [
{
x1: {
type: 'time',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
y1: {
type: 'value',
valScale: 'linear',
min: 'dataMin',
max: 'dataMax',
},
data: [
{
legend: 'PulseSensing',
x1: {
channel: 'time',
},
y1: {
channel: 1,
},
},
],
},
],
},
],
},
}

Some files were not shown because too many files have changed in this diff Show More