15 Commits

Author SHA1 Message Date
peterlu14 6144b60541 [update] update change-note 2022-12-29 10:52:44 +08:00
peterlu14 42f5a37e4d [update] build file system for data analysis 2022-12-29 10:49:19 +08:00
peterlu14 2b4f9bb90c [update] concat data from callback to charts & start button emit 2022-12-06 15:32:22 +08:00
peterlu14 63d0b4d79a [update] add chart api setOption & appendData & markLine & Loading 2022-12-06 14:13:02 +08:00
peterlu14 d7675c0b6a [update] add markLineComponent 2022-12-06 14:11:00 +08:00
peterlu14 1fc202c785 [update] add chart structure 2022-12-06 14:10:18 +08:00
peterlu14 804d95d493 [update] add line chart 2022-12-01 11:52:04 +08:00
peterlu14 8925bbe343 [update] add mqtt method publish & unique client_id 2022-12-01 10:33:19 +08:00
peterlu14 a64f0d93da [update] create new page data analysis 2022-12-01 09:56:15 +08:00
peterlu14 ac3b303b2e [update] import mqtt & echarts 2022-12-01 09:54:55 +08:00
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
313 changed files with 41087 additions and 2519 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: {},
}
+33
View File
@@ -0,0 +1,33 @@
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: {
'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)许可证.
+14
View File
@@ -0,0 +1,14 @@
### 2022.12.30
- file system
### 2022.12.06
- e-chart
- mqtt
- new page data-analysis
### 2022.11.30
- setup environment
- vite config add path
- import windicss
- add vue-shim.d.ts file (TS2307)
+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 -11
View File
@@ -1,18 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="bg-red-500">
<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" />
<link rel="icon" href="/favicon.ico" />
<title>Vuestic Admin</title>
</head>
<body>
<div id="app"></div>
+4865
View File
File diff suppressed because it is too large Load Diff
+62 -26
View File
@@ -1,36 +1,72 @@
{
"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",
"vue-router": "^4.1.6",
"vuestic-ui": "^1.5.2",
"windicss": "^3.5.6"
"@amcharts/amcharts5": "^5.2.19",
"@amcharts/amcharts5-geodata": "^5.0.3",
"@fortawesome/fontawesome-free": "^6.1.1",
"@gtm-support/vue-gtm": "^1.6.0",
"axios": "^0.27.2",
"chart.js": "^3.8.0",
"epic-spinners": "^2.0.0",
"flag-icons": "^6.6.4",
"ionicons": "^4.6.3",
"leaflet": "^1.8.0",
"leaflet-map": "^0.2.1",
"maplibre-gl": "^2.1.9",
"medium-editor": "^5.23.3",
"pinia": "^2.0.16",
"register-service-worker": "^1.7.1",
"sass": "^1.53.0",
"vue": "^3.2.25",
"vue-chartjs": "^4.1.1",
"vue-i18n": "^9.1.10",
"vue-router": "^4.0.14",
"vue-yandex-maps": "^0.12.3",
"vuestic-ui": "^1.5.0"
},
"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",
"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"
"@intlify/vite-plugin-vue-i18n": "^5.0.1",
"@types/leaflet": "^1.7.11",
"@types/medium-editor": "^5.0.5",
"@types/node": "^17.0.25",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0",
"@vitejs/plugin-vue": "^2.3.3",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"autoprefixer": "^10.4.4",
"echarts": "^5.4.0",
"eslint": "^8.13.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.7.0",
"husky": "^8.0.1",
"lint-staged": "^13.0.1",
"mqtt": "^4.3.7",
"postcss": "^8.4.12",
"prettier": "^2.6.2",
"typescript": "^4.5.4",
"vite": "^2.9.9",
"vite-plugin-windicss": "^1.8.8",
"vue-echarts": "^6.2.3",
"vue-eslint-parser": "^8.3.0",
"vue-tsc": "^1.0.10",
"windicss": "^3.5.6"
}
}
-2162
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

+14 -15
View File
@@ -1,19 +1,18 @@
<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;
}
</style>
-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,62 @@
<template>
<div>
<va-card class="h-full">
<va-card-content class="h-full w-full">
<EChart ref="chart_ref" :option="option" :autoresize="true" />
</va-card-content>
</va-card>
</div>
</template>
<script lang="ts">
import { ref, onMounted } from 'vue'
export default {
setup() {
const option = ref({
title: {
text: 'Traffic Sources',
left: 'center',
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)',
},
legend: {
orient: 'vertical',
left: 'left',
data: ['Direct', 'Email', 'Ad Networks', 'Video Ads', 'Search Engines'],
},
series: [
{
name: 'Traffic Sources',
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [
{ value: 335, name: 'Direct' },
{ value: 310, name: 'Email' },
{ value: 234, name: 'Ad Networks' },
{ value: 135, name: 'Video Ads' },
{ value: 1548, name: 'Search Engines' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
})
const chart_ref = ref<HTMLElement | null>(null)
onMounted(() => {
console.log(chart_ref.value)
})
return { option, chart_ref }
},
}
</script>
<style lang=""></style>
@@ -0,0 +1,50 @@
<template>
<div>
<va-card class="h-full">
<va-card-content>
<va-select
v-model="simpleSelectModel"
:label="'Filter'"
text-by="description"
track-by="id"
:options="simpleOptions"
/>
<va-button class="mt-2" @click="filterData">Start</va-button>
<!-- <div v-if="simpleSelectModel.id === 1">
<va-button>test</va-button>
<va-input></va-input>
</div> -->
</va-card-content>
</va-card>
</div>
</template>
<script lang="ts">
import { ref } from 'vue'
export default {
emits: ['filter-data'],
setup(props, { emit }) {
const simpleSelectModel = ref({})
const simpleOptions = ref([
{
id: 1,
description: 'First option',
},
{
id: 2,
description: 'Second option',
},
{
id: 3,
description: 'Third option',
},
])
const filterData = function () {
emit('filter-data')
}
return { props, simpleSelectModel, simpleOptions, filterData }
},
}
</script>
<style lang=""></style>
@@ -0,0 +1,23 @@
<template>
<va-card class="">
<div class="h-200px">
<va-button @click="showModal">FILE</va-button>
<Suspense>
<FileModal ref="file_modal_ref"></FileModal>
</Suspense>
</div>
</va-card>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import FileModal from '@/components/data-analysis/FileModal.vue'
// file modal control
let file_modal_ref = ref<InstanceType<typeof FileModal> | null>(null)
const showModal = function () {
file_modal_ref.value?.showModal()
}
defineExpose({ file_modal_ref, showModal })
</script>
+108
View File
@@ -0,0 +1,108 @@
<template>
<div>
<va-modal v-model="show" :fixed-layout="true" size="large">
<!-- File Container-->
<div class="h-[75vh] w-[50vw]">
<!-- Control Bar -->
<div class="d-flex align-center">
<va-input v-model="filter" placeholder="Filter..." class="mr-3" style="flex: 0 200px" clearable />
<!-- <va-checkbox v-model="isFilterCaseSensitive" label="Case sensitive" /> -->
<va-button-group class="mr-3">
<va-button @click="changeFileView('time')">Time</va-button>
<va-button @click="changeFileView('folder')">Folder</va-button>
<va-button @click="changeFileView('project')">Project</va-button>
</va-button-group>
<va-button-group>
<va-button @click="expandAll(true)">Expand</va-button>
<va-button @click="expandAll(false)">Close</va-button>
</va-button-group>
</div>
<!-- File Window-->
<div class="grid grid-cols-4 gap-6">
<!-- File View-->
<div class="h-full col-span-3">
<va-tree-view
v-model:checked="selectedNodes"
v-model:expanded="expanedNodes"
:nodes="nodes"
class="customizable-content"
:filter="filter"
:filter-method="customFilterMethod"
:track-by="'idType'"
:value-by="'idType'"
selectable
>
<template #content="node">
<div class="align-center">
<va-icon v-if="node.type === 'Folder' && node.expanded === false" class="fas fa-folder" />
<va-icon v-if="node.type === 'Folder' && node.expanded === true" class="fas fa-folder-open" />
<va-icon v-if="node.type === 'MetaFile'" class="fas fa-file" />
{{ node.name }}
</div>
</template>
</va-tree-view>
<!-- <p class="mt-4">Expanded nodes: {{ expanedNodes.join(',') || 'none' }}</p> -->
<p class="mt-4">Selected nodes: {{ selectedNodes.join(',') || 'none' }}</p>
</div>
<!-- Channel & Group setting-->
<div class="h-full col-span-1">XY</div>
</div>
</div>
</va-modal>
</div>
</template>
<script setup lang="ts">
import { Ref, ref, computed } from 'vue'
import { FileView, Project, MetaFile, Folder, Time } from '@/utils/file'
const customFilterMethod = computed(() => {
return (node: Project | MetaFile, filterText: string, key: string) => {
// console.log('customFilterMethod', node, filterText, key, node.name.includes(filterText))
return node.name.includes(filterText)
}
})
// handling show modal
let show = ref(false)
const showModal = function () {
show.value = !show.value
}
// handling init file
const fileView = new FileView()
await fileView.appendChildren(1)
let fileViewChildrenForNodes = fileView.getChildren()
// handling modal header
const changeFileView = async function (view: string) {
fileView.changeView(view)
await fileView.appendChildren(1)
fileViewChildrenForNodes = fileView.getChildren()
nodes.value.length = 0
nodes.value.push(...fileViewChildrenForNodes)
expanedNodes.value.length = 0
expanedNodes.value.push(nodes.value[0].idType)
}
const expandAll = function (expandOrNot: boolean) {
expanedNodes.value.length = 0
if (!expandOrNot) return
for (const node of nodes.value) {
expanedNodes.value.push(node.idType)
}
}
// handling tree view
const filter = ref('')
const isFilterCaseSensitive = ref(false)
const selectedNodes = ref([])
const expanedNodes: Ref<(never | string)[]> = ref([])
const nodes: Ref<(never | Project | Folder | Time | MetaFile)[]> = ref([])
nodes.value.push(...fileViewChildrenForNodes)
expanedNodes.value.push(nodes.value[0].idType)
console.log('nodes', nodes)
defineExpose({ show, showModal, nodes, expanedNodes, selectedNodes })
</script>
<style lang="scss"></style>
@@ -0,0 +1,63 @@
<template>
<div>
<va-card class="h-full">
<va-card-content class="h-full w-full">
<EChart ref="chart_ref" :option="option" :autoresize="true" :loading="loading" />
</va-card-content>
</va-card>
</div>
</template>
<script setup lang="ts">
import LineChart from '@/data/echart/composables/LineChart'
import { EChartsCoreOption } from 'echarts'
import { ref } from 'vue'
const loading = ref(false)
const { chart_ref, option } = LineChart()
const setOption = function (option: EChartsCoreOption) {
chart_ref.value?.setOption(option)
}
const appendData = function (seriesIndex: number, data: Array<number | string>) {
chart_ref.value?.appendData({
seriesIndex: seriesIndex,
data: data,
})
chart_ref.value?.resize()
}
const markLine = function (
seriesIndex: number,
markLineIndex: number,
header: string,
value: number | Array<number> | string,
) {
chart_ref.value?.setOption({
series: [
{
markLine: {
data: [{ name: header, yAxis: value }],
},
},
],
})
}
const startLoading = function () {
loading.value = true
}
const stopLoading = function () {
loading.value = false
}
defineExpose({
startLoading,
stopLoading,
setOption,
appendData,
markLine,
})
</script>
<style lang=""></style>
+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: 'dashboard',
displayName: 'menu.dashboard',
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>
+46
View File
@@ -0,0 +1,46 @@
<template>
<component :is="chartComponent" ref="chart" class="va-chart" :chart-options="chartOptions" :chart-data="data" />
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { TChartOptions } from 'vue-chartjs/dist/types'
import { defaultConfig, chartTypesMap } from './vaChartConfigs'
import { TChartData } from '../../data/types'
const props = defineProps<{
data: TChartData
options?: TChartOptions<'line' | 'bar' | 'bubble' | 'doughnut' | 'pie'>
type: keyof typeof chartTypesMap
}>()
const chart = ref()
const chartComponent = computed(() => chartTypesMap[props.type])
const chartOptions = computed(() => ({
...defaultConfig,
...props.options,
}))
</script>
<style lang="scss">
.va-chart {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
> * {
height: 100%;
width: 100%;
}
canvas {
width: 100%;
height: auto;
min-height: 320px;
}
}
</style>
@@ -0,0 +1,17 @@
<template>
<Bar :chart-options="chartOptions" :chart-data="props.chartData" />
</template>
<script setup lang="ts">
import { Bar } from 'vue-chartjs'
import type { TChartOptions } from 'vue-chartjs/dist/types'
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js'
import { TBarChartData } from '../../../data/types'
ChartJS.register(Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale)
const props = defineProps<{
chartData: TBarChartData
chartOptions?: TChartOptions<'bar'>
}>()
</script>
@@ -0,0 +1,17 @@
<template>
<Bubble :chart-options="chartOptions" :chart-data="props.chartData" />
</template>
<script setup lang="ts">
import { Bubble } from 'vue-chartjs'
import type { TChartOptions } from 'vue-chartjs/dist/types'
import { Chart as ChartJS, Title, Tooltip, Legend, PointElement, LinearScale } from 'chart.js'
import { TBubbleChartData } from '../../../data/types'
ChartJS.register(Title, Tooltip, Legend, PointElement, LinearScale)
const props = defineProps<{
chartData: TBubbleChartData
chartOptions?: TChartOptions<'bubble'>
}>()
</script>
@@ -0,0 +1,17 @@
<template>
<Doughnut :chart-options="chartOptions" :chart-data="props.chartData" />
</template>
<script setup lang="ts">
import { Doughnut } from 'vue-chartjs'
import type { TChartOptions } from 'vue-chartjs/dist/types'
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale } from 'chart.js'
import { TDoughnutChartData } from '../../../data/types'
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale)
const props = defineProps<{
chartData: TDoughnutChartData
chartOptions?: TChartOptions<'doughnut'>
}>()
</script>
@@ -0,0 +1,26 @@
<template>
<Bar :chart-options="{ ...chartOptions, ...horizontalBarOptions }" :chart-data="props.chartData" />
</template>
<script setup lang="ts">
import { Bar } from 'vue-chartjs'
import type { TChartOptions } from 'vue-chartjs/dist/types'
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js'
import { TBarChartData } from '../../../data/types'
ChartJS.register(Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale)
const horizontalBarOptions = {
indexAxis: 'y' as 'x' | 'y',
elements: {
bar: {
borderWidth: 1,
},
},
}
const props = defineProps<{
chartData: TBarChartData
chartOptions?: TChartOptions<'bar'>
}>()
</script>
@@ -0,0 +1,27 @@
<template>
<Line :chart-options="chartOptions" :chart-data="props.chartData" />
</template>
<script setup lang="ts">
import { Line } from 'vue-chartjs'
import type { TChartOptions } from 'vue-chartjs/dist/types'
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
PointElement,
CategoryScale,
Filler,
} from 'chart.js'
import { TLineChartData } from '../../../data/types'
ChartJS.register(Title, Tooltip, Legend, LineElement, LinearScale, PointElement, CategoryScale, Filler)
const props = defineProps<{
chartData: TLineChartData
chartOptions?: TChartOptions<'line'>
}>()
</script>
@@ -0,0 +1,17 @@
<template>
<Pie :chart-options="chartOptions" :chart-data="props.chartData" />
</template>
<script setup lang="ts">
import { Pie } from 'vue-chartjs'
import type { TChartOptions } from 'vue-chartjs/dist/types'
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale } from 'chart.js'
import { TPieChartData } from '../../../data/types'
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale)
const props = defineProps<{
chartData: TPieChartData
chartOptions?: TChartOptions<'pie'>
}>()
</script>
@@ -0,0 +1,48 @@
import { defineAsyncComponent } from 'vue'
export const defaultConfig = {
plugins: {
legend: {
position: 'bottom',
labels: {
font: {
color: '#34495e',
family: 'sans-serif',
size: 14,
},
usePointStyle: true,
},
},
tooltip: {
bodyFont: {
size: 14,
family: 'sans-serif',
},
boxPadding: 4,
},
},
datasets: {
line: {
fill: 'origin',
tension: 0.3,
borderColor: 'transparent',
},
bubble: {
borderColor: 'transparent',
},
bar: {
borderColor: 'transparent',
},
},
maintainAspectRatio: false,
animation: true,
}
export const chartTypesMap = {
pie: defineAsyncComponent(() => import('./chart-types/PieChart.vue')),
doughnut: defineAsyncComponent(() => import('./chart-types/DoughnutChart.vue')),
bubble: defineAsyncComponent(() => import('./chart-types/BubbleChart.vue')),
line: defineAsyncComponent(() => import('./chart-types/LineChart.vue')),
bar: defineAsyncComponent(() => import('./chart-types/BarChart.vue')),
'horizontal-bar': defineAsyncComponent(() => import('./chart-types/HorizontalBarChart.vue')),
}
@@ -0,0 +1,189 @@
<template>
<div ref="editorElement" class="va-medium-editor content">
<slot />
</div>
</template>
<script setup lang="ts">
import { ref, Ref, onMounted, onBeforeUnmount } from 'vue'
import MediumEditor from 'medium-editor'
const props = withDefaults(
defineProps<{
editorOptions?: {
buttonLabels: string
autoLink: boolean
toolbar: {
buttons: string[]
}
}
}>(),
{
editorOptions: () => ({
buttonLabels: 'fontawesome',
autoLink: true,
toolbar: {
buttons: ['bold', 'italic', 'underline', 'anchor', 'h1', 'h2', 'h3'],
},
}),
},
)
const emit = defineEmits<{
(e: 'initialized', editor: typeof MediumEditor): void
}>()
const editorElement: Ref<null | HTMLElement> = ref(null)
let editor: typeof MediumEditor | null = null
onMounted(() => {
if (!editorElement.value) {
return
}
editor = new MediumEditor(editorElement.value, props.editorOptions)
emit('initialized', editor)
})
onBeforeUnmount(() => {
if (editor) {
editor.destroy()
}
})
</script>
<style lang="scss">
@import 'medium-editor/src/sass/medium-editor';
@import 'variables';
$medium-editor-shadow: var(--va-box-shadow);
$medium-editor-background-color: var(--va-divider);
$medium-editor-text-color: var(--va-dark);
$medium-editor-active-background-color: var(--va-primary);
$medium-editor-active-text-color: var(--va-white);
.va-medium-editor {
margin-bottom: var(--va-medium-editor-margin-bottom);
min-width: var(--va-medium-editor-min-width);
max-width: var(--va-medium-editor-max-width);
&:focus {
outline: none;
}
&.content {
i {
font-style: italic;
}
}
}
// isn't a part of the .va-medium-editor, so can't be places inside it
.medium-editor-toolbar,
.medium-editor-toolbar-form,
.medium-editor-toolbar-actions,
.medium-editor-toolbar-anchor-preview {
box-shadow: $medium-editor-shadow;
background-color: $medium-editor-background-color;
border-radius: 1.5rem;
height: 44px;
line-height: 42px;
}
.medium-editor-toolbar-anchor-preview {
a {
padding: 0 2rem;
margin: 0;
line-height: 44px;
}
}
.medium-editor-toolbar {
box-shadow: $medium-editor-shadow;
.medium-editor-toolbar-actions {
overflow: hidden;
height: 44px;
}
.medium-editor-action {
margin: 0;
border: 0;
padding: 0.375rem 1rem;
height: 44px;
background-color: $medium-editor-background-color;
box-shadow: none;
border-radius: 0;
i {
color: $medium-editor-text-color;
}
&.medium-editor-button-active {
background-color: $medium-editor-active-background-color;
color: $medium-editor-active-text-color;
i {
color: $medium-editor-active-text-color;
}
}
}
& > .medium-editor-action:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 0;
}
& > .medium-editor-action + .medium-editor-action {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 0;
}
}
.medium-editor-toolbar-form {
color: $medium-editor-text-color;
overflow: hidden;
a {
color: $medium-editor-text-color;
transform: translateY(1px);
}
input {
margin-left: 4px !important;
transform: translateY(-2px);
border-radius: 13px;
}
.medium-editor-toolbar-close {
margin-right: 1rem;
}
}
.medium-toolbar-arrow-under::after {
border-color: $medium-editor-background-color transparent transparent transparent;
top: 100%;
}
.medium-toolbar-arrow-over::before {
border-color: transparent transparent var(--va-primary) transparent;
}
.medium-editor-toolbar-anchor-preview {
// @include va-button($btn-padding-y-nrm, $btn-padding-x-nrm, $btn-font-size-nrm, $btn-line-height-nrm, $btn-border-radius-nrm);
.medium-editor-toolbar-anchor-preview {
margin: 0;
}
}
.medium-editor-anchor-preview {
max-width: 50%;
a {
color: $medium-editor-text-color;
text-decoration: none;
}
}
</style>
@@ -0,0 +1,9 @@
:root {
--va-medium-editor-margin-bottom: 2.25rem;
--va-medium-editor-min-width: 6rem;
--va-medium-editor-max-width: 600px;
/* Toolbar */
--va-medium-editor-toolbar-max-width: 90%;
--va-medium-editor-toolbar-box-shadow: none;
}
+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,
}
}
+243
View File
@@ -0,0 +1,243 @@
export default [
'Afghanistan',
'Albania',
'Algeria',
'American Samoa',
'Andorra',
'Angola',
'Anguilla',
'Antarctica',
'Antigua and Barbuda',
'Argentina',
'Armenia',
'Aruba',
'Australia',
'Austria',
'Azerbaijan',
'Bahamas',
'Bahrain',
'Bangladesh',
'Barbados',
'Belarus',
'Belgium',
'Belize',
'Benin',
'Bermuda',
'Bhutan',
'Bolivia',
'Bosnia and Herzegowina',
'Botswana',
'Bouvet Island',
'Brazil',
'British Indian Ocean Territory',
'Brunei Darussalam',
'Bulgaria',
'Burkina Faso',
'Burundi',
'Cambodia',
'Cameroon',
'Canada',
'Cape Verde',
'Cayman Islands',
'Central African Republic',
'Chad',
'Chile',
'China',
'Christmas Island',
'Cocos (Keeling) Islands',
'Colombia',
'Comoros',
'Congo',
'Congo, the Democratic Republic of the',
'Cook Islands',
'Costa Rica',
"Cote d'Ivoire",
'Croatia (Hrvatska)',
'Cuba',
'Cyprus',
'Czech Republic',
'Denmark',
'Djibouti',
'Dominica',
'Dominican Republic',
'East Timor',
'Ecuador',
'Egypt',
'El Salvador',
'Equatorial Guinea',
'Eritrea',
'Estonia',
'Ethiopia',
'Falkland Islands (Malvinas)',
'Faroe Islands',
'Fiji',
'Finland',
'France',
'France Metropolitan',
'French Guiana',
'French Polynesia',
'French Southern Territories',
'Gabon',
'Gambia',
'Georgia',
'Germany',
'Ghana',
'Gibraltar',
'Greece',
'Greenland',
'Grenada',
'Guadeloupe',
'Guam',
'Guatemala',
'Guinea',
'Guinea-Bissau',
'Guyana',
'Haiti',
'Heard and Mc Donald Islands',
'Holy See (Vatican City State)',
'Honduras',
'Hong Kong',
'Hungary',
'Iceland',
'India',
'Indonesia',
'Iran (Islamic Republic of)',
'Iraq',
'Ireland',
'Israel',
'Italy',
'Jamaica',
'Japan',
'Jordan',
'Kazakhstan',
'Kenya',
'Kiribati',
"Korea, Democratic People's Republic of",
'Korea, Republic of',
'Kuwait',
'Kyrgyzstan',
"Lao, People's Democratic Republic",
'Latvia',
'Lebanon',
'Lesotho',
'Liberia',
'Libyan Arab Jamahiriya',
'Liechtenstein',
'Lithuania',
'Luxembourg',
'Macau',
'Macedonia, The Former Yugoslav Republic of',
'Madagascar',
'Malawi',
'Malaysia',
'Maldives',
'Mali',
'Malta',
'Marshall Islands',
'Martinique',
'Mauritania',
'Mauritius',
'Mayotte',
'Mexico',
'Micronesia, Federated States of',
'Moldova, Republic of',
'Monaco',
'Mongolia',
'Montserrat',
'Morocco',
'Mozambique',
'Myanmar',
'Namibia',
'Nauru',
'Nepal',
'Netherlands',
'Netherlands Antilles',
'New Caledonia',
'New Zealand',
'Nicaragua',
'Niger',
'Nigeria',
'Niue',
'Norfolk Island',
'Northern Mariana Islands',
'Norway',
'Oman',
'Pakistan',
'Palau',
'Panama',
'Papua New Guinea',
'Paraguay',
'Peru',
'Philippines',
'Pitcairn',
'Poland',
'Portugal',
'Puerto Rico',
'Qatar',
'Reunion',
'Romania',
'Russian Federation',
'Rwanda',
'Saint Kitts and Nevis',
'Saint Lucia',
'Saint Vincent and the Grenadines',
'Samoa',
'San Marino',
'Sao Tome and Principe',
'Saudi Arabia',
'Senegal',
'Serbia',
'Seychelles',
'Sierra Leone',
'Singapore',
'Slovakia (Slovak Republic)',
'Slovenia',
'Solomon Islands',
'Somalia',
'South Africa',
'South Georgia and the South Sandwich Islands',
'Spain',
'Sri Lanka',
'St. Helena',
'St. Pierre and Miquelon',
'Sudan',
'Suriname',
'Svalbard and Jan Mayen Islands',
'Swaziland',
'Sweden',
'Switzerland',
'Syrian Arab Republic',
'Taiwan, Province of China',
'Tajikistan',
'Tanzania, United Republic of',
'United States of America',
'Thailand',
'Togo',
'Tokelau',
'Tonga',
'Trinidad and Tobago',
'Tunisia',
'Turkey',
'Turkmenistan',
'Turks and Caicos Islands',
'Tuvalu',
'Uganda',
'Ukraine',
'United Arab Emirates',
'United Kingdom',
'United States',
'United States Minor Outlying Islands',
'Uruguay',
'Uzbekistan',
'Vanuatu',
'Venezuela',
'Vietnam',
'Virgin Islands (British)',
'Virgin Islands (U.S.)',
'Wallis and Futuna Islands',
'Western Sahara',
'Yemen',
'Yugoslavia',
'Zambia',
'Zimbabwe',
]
+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
}
+30
View File
@@ -0,0 +1,30 @@
import { TBarChartData } from '../types'
export const barChartData: TBarChartData = {
labels: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
],
datasets: [
{
label: 'Last year',
backgroundColor: 'primary',
data: [50, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11],
},
{
label: 'Current year',
backgroundColor: 'info',
data: [50, 10, 22, 39, 15, 20, 85, 32, 60, 50, 20, 30],
},
],
}
+231
View File
@@ -0,0 +1,231 @@
import { TBubbleChartData } from '../types'
export const bubbleChartData: TBubbleChartData = {
datasets: [
{
label: 'USA',
backgroundColor: 'danger',
data: [
{
x: 23,
y: 25,
r: 15,
},
{
x: 40,
y: 10,
r: 10,
},
{
x: 30,
y: 22,
r: 30,
},
{
x: 7,
y: 43,
r: 40,
},
{
x: 23,
y: 27,
r: 12,
},
{
x: 20,
y: 15,
r: 11,
},
{
x: 7,
y: 10,
r: 35,
},
{
x: 10,
y: 20,
r: 40,
},
],
},
{
label: 'Russia',
backgroundColor: 'primary',
data: [
{
x: 0,
y: 30,
r: 15,
},
{
x: 20,
y: 20,
r: 20,
},
{
x: 15,
y: 15,
r: 50,
},
{
x: 31,
y: 46,
r: 30,
},
{
x: 20,
y: 14,
r: 25,
},
{
x: 34,
y: 17,
r: 30,
},
{
x: 44,
y: 44,
r: 10,
},
{
x: 39,
y: 25,
r: 35,
},
],
},
{
label: 'Canada',
backgroundColor: 'warning',
data: [
{
x: 10,
y: 30,
r: 45,
},
{
x: 10,
y: 50,
r: 20,
},
{
x: 5,
y: 5,
r: 30,
},
{
x: 40,
y: 30,
r: 20,
},
{
x: 33,
y: 15,
r: 18,
},
{
x: 40,
y: 20,
r: 40,
},
{
x: 33,
y: 33,
r: 40,
},
],
},
{
label: 'Belarus',
backgroundColor: 'info',
data: [
{
x: 35,
y: 30,
r: 45,
},
{
x: 25,
y: 40,
r: 35,
},
{
x: 5,
y: 5,
r: 30,
},
{
x: 5,
y: 20,
r: 40,
},
{
x: 10,
y: 40,
r: 15,
},
{
x: 3,
y: 10,
r: 10,
},
{
x: 15,
y: 40,
r: 40,
},
{
x: 7,
y: 15,
r: 10,
},
],
},
{
label: 'Ukraine',
backgroundColor: 'success',
data: [
{
x: 25,
y: 10,
r: 40,
},
{
x: 17,
y: 40,
r: 40,
},
{
x: 35,
y: 10,
r: 20,
},
{
x: 3,
y: 40,
r: 10,
},
{
x: 40,
y: 40,
r: 40,
},
{
x: 20,
y: 10,
r: 10,
},
{
x: 10,
y: 27,
r: 35,
},
{
x: 7,
y: 26,
r: 40,
},
],
},
],
}
@@ -0,0 +1,35 @@
import { computed, ref } from '@vue/reactivity'
import { watch } from 'vue'
import { useColors, useGlobalConfig } from 'vuestic-ui'
type chartColors = string | string[]
export function useChartColors(chartColors = [] as chartColors, alfa = 0.6) {
const { getGlobalConfig } = useGlobalConfig()
const { setHSLAColor, getColor } = useColors()
const generateHSLAColors = (colors: chartColors) =>
typeof colors === 'string'
? setHSLAColor(getColor(colors), { a: alfa })
: colors.map((color) => setHSLAColor(getColor(color), { a: alfa }))
const generateColors = (colors: chartColors) =>
typeof colors === 'string' ? getColor(colors) : colors.map((color) => getColor(color))
const generatedHSLAColors = ref(generateHSLAColors(chartColors))
const generatedColors = ref(generateColors(chartColors))
const theme = computed(() => getGlobalConfig().colors!)
watch(theme, () => {
generatedHSLAColors.value = generateHSLAColors(chartColors)
generatedColors.value = generateColors(chartColors)
})
return {
generateHSLAColors,
generateColors,
generatedColors,
generatedHSLAColors,
}
}
@@ -0,0 +1,20 @@
import { computed, ComputedRef } from '@vue/reactivity'
import { useChartColors } from './useChartColors'
import { TChartData } from '../../types'
export function useChartData<T extends TChartData>(data: T, alfa?: number): ComputedRef<T> {
const datasetsColors = data.datasets.map((dataset) => dataset.backgroundColor as string)
const datasetsThemesColors = datasetsColors.map(
(colors) => useChartColors(colors, alfa)[alfa ? 'generatedHSLAColors' : 'generatedColors'],
)
return computed(() => {
const datasets = data.datasets.map((dataset, idx) => ({
...dataset,
backgroundColor: datasetsThemesColors[idx].value,
}))
return { ...data, datasets } as T
})
}
+12
View File
@@ -0,0 +1,12 @@
import { TDoughnutChartData } from '../types'
export const doughnutChartData: TDoughnutChartData = {
labels: ['North America', 'South America', 'Australia'],
datasets: [
{
label: 'Population (millions)',
backgroundColor: ['danger', 'info', 'primary'],
data: [2478, 5267, 734],
},
],
}
+30
View File
@@ -0,0 +1,30 @@
import { TBarChartData } from '../types'
export const horizontalBarChartData: TBarChartData = {
labels: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
],
datasets: [
{
label: 'Vuestic Satisfaction Score',
backgroundColor: 'primary',
data: [80, 90, 50, 70, 60, 90, 50, 90, 80, 40, 72, 93],
},
{
label: 'Bulma Satisfaction Score',
backgroundColor: 'danger',
data: [20, 30, 20, 40, 50, 40, 15, 60, 30, 20, 42, 53],
},
],
}
+6
View File
@@ -0,0 +1,6 @@
export { bubbleChartData } from './bubbleChartData'
export { doughnutChartData } from './doughnutChartData'
export { barChartData } from './barChartData'
export { horizontalBarChartData } from './horizontalBarChartData'
export { lineChartData } from './lineChartData'
export { pieChartData } from './pieChartData'
+44
View File
@@ -0,0 +1,44 @@
import { TLineChartData } from '../types'
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
]
const getSize = (minSize = 5) => Math.max(minSize, new Date().getMonth())
const size = getSize()
const generateValue = () => Math.floor(Math.random() * 100)
const generateArray = (length: number) => Array.from(Array(length), generateValue)
const generateYLabels = () => {
const flip = !!Math.floor(Math.random() * 2)
return flip ? ['Debit', 'Credit'] : ['Credit', 'Debit']
}
const yLabels = generateYLabels()
export const lineChartData: TLineChartData = {
labels: months.slice(0, size),
datasets: [
{
label: yLabels[0],
backgroundColor: 'primary',
data: generateArray(size),
},
{
label: yLabels[1],
backgroundColor: 'secondary',
data: generateArray(size),
},
],
}
+12
View File
@@ -0,0 +1,12 @@
import { TLineChartData } from '../types'
export const pieChartData: TLineChartData = {
labels: ['Africa', 'Asia', 'Europe'],
datasets: [
{
label: 'Population (millions)',
backgroundColor: ['primary', 'warning', 'danger'],
data: [2478, 5267, 734],
},
],
}
+20
View File
@@ -0,0 +1,20 @@
import { EChartsOption } from 'echarts'
import { generateLegend } from './legend'
import { generateGrid } from './grid'
import { generateXAxis } from './xAxis'
import { generateYAxis } from './yAxis'
import { generateTooltip } from './tooltip'
import { generateSeries } from './series'
import { generateDataZoom } from './datazoom'
export const generateChart = function (): EChartsOption {
return {
legend: generateLegend(),
tooltip: generateTooltip(),
grid: [generateGrid()],
xAxis: [generateXAxis()],
yAxis: [generateYAxis()],
series: [generateSeries('')],
dataZoom: generateDataZoom(),
}
}
+10
View File
@@ -0,0 +1,10 @@
import { ref } from 'vue'
import { ECharts } from 'echarts'
import { generateChart } from '@/data/echart/chart'
export default function lineChart() {
const chart_ref = ref<ECharts | null>(null)
const option = generateChart()
return { option, chart_ref }
}
+20
View File
@@ -0,0 +1,20 @@
import { DataZoomComponentOption } from 'echarts'
export const generateDataZoom = function (): DataZoomComponentOption | DataZoomComponentOption[] {
return [
{
show: true,
realtime: true,
start: 0,
end: 100,
xAxisIndex: [0, 1],
},
{
type: 'inside',
realtime: true,
start: 0,
end: 100,
xAxisIndex: [0, 1],
},
]
}
+5
View File
@@ -0,0 +1,5 @@
import { TooltipComponentFormatterCallbackParams } from 'echarts'
export const generateFormatter = function (params: TooltipComponentFormatterCallbackParams): string {
return String(params[0].value)
}
+18
View File
@@ -0,0 +1,18 @@
import { GridComponentOption } from 'echarts'
export const generateGrid = function (): GridComponentOption {
const index = 0
const length = 1
// const chartID = 1
const _height = 82 / length - 2 + '%'
const _top = index * (82 / length) + 1 + '%'
const _left = '8%'
const _right = '8%'
return {
top: _top,
height: _height,
left: _left,
right: _right,
}
}
+14
View File
@@ -0,0 +1,14 @@
import { LegendComponentOption } from 'echarts'
export const generateLegend = function (): LegendComponentOption {
return {
type: 'scroll',
data: [],
// x: 'center',
// y: '-10%',
bottom: '0%',
textStyle: {
color: '#34495e',
},
}
}

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