Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67cca1ee1a | |||
| 48e8d2c7c3 |
4
.browserslistrc
Normal file
4
.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
|
not dead
|
||||||
|
not ie 11
|
||||||
5
.editorconfig
Normal file
5
.editorconfig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[*.{js,jsx,ts,tsx,vue}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
79
README.md
Normal file
79
README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Vuetify (Default)
|
||||||
|
|
||||||
|
This is the official scaffolding tool for Vuetify, designed to give you a head start in building your new Vuetify application. It sets up a base template with all the necessary configurations and standard directory structure, enabling you to begin development without the hassle of setting up the project from scratch.
|
||||||
|
|
||||||
|
## ❗️ Important Links
|
||||||
|
|
||||||
|
- 📄 [Docs](https://vuetifyjs.com/)
|
||||||
|
- 🚨 [Issues](https://issues.vuetifyjs.com/)
|
||||||
|
- 🏬 [Store](https://store.vuetifyjs.com/)
|
||||||
|
- 🎮 [Playground](https://play.vuetifyjs.com/)
|
||||||
|
- 💬 [Discord](https://community.vuetifyjs.com)
|
||||||
|
|
||||||
|
## 💿 Install
|
||||||
|
|
||||||
|
Set up your project using your preferred package manager. Use the corresponding command to install the dependencies:
|
||||||
|
|
||||||
|
| Package Manager | Command |
|
||||||
|
|---------------------------------------------------------------|----------------|
|
||||||
|
| [yarn](https://yarnpkg.com/getting-started) | `yarn install` |
|
||||||
|
| [npm](https://docs.npmjs.com/cli/v7/commands/npm-install) | `npm install` |
|
||||||
|
| [pnpm](https://pnpm.io/installation) | `pnpm install` |
|
||||||
|
| [bun](https://bun.sh/#getting-started) | `bun install` |
|
||||||
|
|
||||||
|
After completing the installation, your environment is ready for Vuetify development.
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- 🖼️ **Optimized Front-End Stack**: Leverage the latest Vue 3 and Vuetify 3 for a modern, reactive UI development experience. [Vue 3](https://v3.vuejs.org/) | [Vuetify 3](https://vuetifyjs.com/en/)
|
||||||
|
- 🗃️ **State Management**: Integrated with [Pinia](https://pinia.vuejs.org/), the intuitive, modular state management solution for Vue.
|
||||||
|
- 🚦 **Routing and Layouts**: Utilizes Vue Router for SPA navigation and vite-plugin-vue-layouts for organizing Vue file layouts. [Vue Router](https://router.vuejs.org/) | [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts)
|
||||||
|
- ⚡ **Next-Gen Tooling**: Powered by Vite, experience fast cold starts and instant HMR (Hot Module Replacement). [Vite](https://vitejs.dev/)
|
||||||
|
- 🧩 **Automated Component Importing**: Streamline your workflow with unplugin-vue-components, automatically importing components as you use them. [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components)
|
||||||
|
|
||||||
|
These features are curated to provide a seamless development experience from setup to deployment, ensuring that your Vuetify application is both powerful and maintainable.
|
||||||
|
|
||||||
|
## 💡 Usage
|
||||||
|
|
||||||
|
This section covers how to start the development server and build your project for production.
|
||||||
|
|
||||||
|
### Starting the Development Server
|
||||||
|
|
||||||
|
To start the development server with hot-reload, run the following command. The server will be accessible at [http://localhost:3000](http://localhost:3000):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
(Repeat for npm, pnpm, and bun with respective commands.)
|
||||||
|
|
||||||
|
> Add NODE_OPTIONS='--no-warnings' to suppress the JSON import warnings that happen as part of the Vuetify import mapping. If you are on Node [v21.3.0](https://nodejs.org/en/blog/release/v21.3.0) or higher, you can change this to NODE_OPTIONS='--disable-warning=5401'. If you don't mind the warning, you can remove this from your package.json dev script.
|
||||||
|
|
||||||
|
### Building for Production
|
||||||
|
|
||||||
|
To build your project for production, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
(Repeat for npm, pnpm, and bun with respective commands.)
|
||||||
|
|
||||||
|
Once the build process is completed, your application will be ready for deployment in a production environment.
|
||||||
|
|
||||||
|
## 💪 Support Vuetify Development
|
||||||
|
|
||||||
|
This project is built with [Vuetify](https://vuetifyjs.com/en/), a UI Library with a comprehensive collection of Vue components. Vuetify is an MIT licensed Open Source project that has been made possible due to the generous contributions by our [sponsors and backers](https://vuetifyjs.com/introduction/sponsors-and-backers/). If you are interested in supporting this project, please consider:
|
||||||
|
|
||||||
|
- [Requesting Enterprise Support](https://support.vuetifyjs.com/)
|
||||||
|
- [Sponsoring John on Github](https://github.com/users/johnleider/sponsorship)
|
||||||
|
- [Sponsoring Kael on Github](https://github.com/users/kaelwd/sponsorship)
|
||||||
|
- [Supporting the team on Open Collective](https://opencollective.com/vuetify)
|
||||||
|
- [Becoming a sponsor on Patreon](https://www.patreon.com/vuetify)
|
||||||
|
- [Becoming a subscriber on Tidelift](https://tidelift.com/subscription/npm/vuetify)
|
||||||
|
- [Making a one-time donation with Paypal](https://paypal.me/vuetify)
|
||||||
|
|
||||||
|
## 📑 License
|
||||||
|
[MIT](http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016-present Vuetify, LLC
|
||||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Welcome to Vuetify 3</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
jsconfig.json
Normal file
20
jsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"target": "es5",
|
||||||
|
"module": "esnext",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
3256
package-lock.json
generated
Normal file
3256
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "mockup",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/roboto": "5.2.7",
|
||||||
|
"@mdi/font": "7.4.47",
|
||||||
|
"@ygoe/msgpack": "^1.0.3",
|
||||||
|
"axios": "^1.12.2",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
|
"vue": "^3.5.21",
|
||||||
|
"vuetify": "^3.10.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"sass-embedded": "^1.92.1",
|
||||||
|
"unplugin-fonts": "^1.4.0",
|
||||||
|
"unplugin-vue-components": "^29.0.0",
|
||||||
|
"vite": "^7.1.5",
|
||||||
|
"vite-plugin-vuetify": "^2.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
95
src/App.vue
Normal file
95
src/App.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<v-app>
|
||||||
|
<v-app-bar title="CMBlu BMS">
|
||||||
|
<v-btn @click.prevent="changeTheme" icon="mdi-theme-light-dark"></v-btn>
|
||||||
|
<v-app-bar-nav-icon><v-icon>mdi-account</v-icon></v-app-bar-nav-icon>
|
||||||
|
</v-app-bar>
|
||||||
|
<v-navigation-drawer expand-on-hover permanent rail>
|
||||||
|
<v-list density="compact" nav>
|
||||||
|
<v-list-item
|
||||||
|
prepend-icon="mdi-home-city"
|
||||||
|
title="Home"
|
||||||
|
value="home"
|
||||||
|
></v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
prepend-icon="mdi-hand-back-left"
|
||||||
|
title="Manual"
|
||||||
|
value="manual"
|
||||||
|
></v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
prepend-icon="mdi-bell"
|
||||||
|
title="Alerts"
|
||||||
|
value="alerts"
|
||||||
|
></v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
prepend-icon="mdi-cog"
|
||||||
|
title="Settings"
|
||||||
|
value="settings"
|
||||||
|
></v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-navigation-drawer>
|
||||||
|
<v-main>
|
||||||
|
<v-container fluid class="pa-6">
|
||||||
|
<v-row>
|
||||||
|
<!-- <v-col cols="12" md="4">
|
||||||
|
<automatic-mode
|
||||||
|
:max-power="18000"
|
||||||
|
:min-power="-18000"
|
||||||
|
node-id-can-change-mode="ns=4;s=GVL_SCADA.xCanChangeControlMode"
|
||||||
|
node-id-current-control-mode="ns=4;s=GVL_SCADA.eCurrentControlMode"
|
||||||
|
class="ma-2"
|
||||||
|
></automatic-mode>
|
||||||
|
</v-col> -->
|
||||||
|
<v-col cols="12">
|
||||||
|
<!-- <battery-state class="ma-2"></battery-state> -->
|
||||||
|
<!-- <module-view
|
||||||
|
module-base-node-id="ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule1"
|
||||||
|
/> -->
|
||||||
|
<events-table />
|
||||||
|
<!-- <test-table /> -->
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-main>
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AutomaticMode from "@/components/AutomaticMode.vue";
|
||||||
|
import BatteryState from "@/components/BatteryState.vue";
|
||||||
|
import ModuleSemiAuto from "@/components/ModuleSemiAuto.vue";
|
||||||
|
import ModuleView from "./components/ModuleView.vue";
|
||||||
|
import ValveControl from "./components/BaseComponents/ValveControl.vue";
|
||||||
|
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
import { useOpcuaStore } from "@/stores/opcua";
|
||||||
|
import EventsTable from "./components/EventsTable.vue";
|
||||||
|
import TestTable from "./components/TestTable.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "App",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentPage: "EventView",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
AutomaticMode,
|
||||||
|
BatteryState,
|
||||||
|
ModuleView,
|
||||||
|
EventsTable,
|
||||||
|
TestTable
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.connect();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeTheme() {
|
||||||
|
this.$vuetify.theme.toggle();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
6
src/assets/logo.svg
Normal file
6
src/assets/logo.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M261.126 140.65L164.624 307.732L256.001 466L377.028 256.5L498.001 47H315.192L261.126 140.65Z" fill="#1697F6"/>
|
||||||
|
<path d="M135.027 256.5L141.365 267.518L231.64 111.178L268.731 47H256H14L135.027 256.5Z" fill="#AEDDFF"/>
|
||||||
|
<path d="M315.191 47C360.935 197.446 256 466 256 466L164.624 307.732L315.191 47Z" fill="#1867C0"/>
|
||||||
|
<path d="M268.731 47C76.0026 47 141.366 267.518 141.366 267.518L268.731 47Z" fill="#7BC6FF"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 526 B |
107
src/components/AutomaticMode.vue
Normal file
107
src/components/AutomaticMode.vue
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<v-card width="250" title="Battery Control">
|
||||||
|
<v-card-text>
|
||||||
|
<v-form>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" class="d-flex flex-column">
|
||||||
|
<v-select
|
||||||
|
label="Operation mode"
|
||||||
|
v-model="opcuaStore.values[nodeIdCurrentControlMode]"
|
||||||
|
:items="modes"
|
||||||
|
item-title="title"
|
||||||
|
item-value="value"
|
||||||
|
:disabled="!opcuaStore.values[nodeIdCanChangeMode]"
|
||||||
|
></v-select>
|
||||||
|
<v-text-field
|
||||||
|
v-model="requestedPower"
|
||||||
|
:rules="checkReqPower"
|
||||||
|
:reverse="true"
|
||||||
|
:disabled="opcuaStore.values[nodeIdCurrentControlMode] != 2"
|
||||||
|
prefix="W"
|
||||||
|
@focus="$event.target.select()"
|
||||||
|
@keyup.enter="$event.target.blur()"
|
||||||
|
label="Requested power"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn
|
||||||
|
@click.prevent=""
|
||||||
|
:disabled="opcuaStore.values[nodeIdCurrentControlMode] != 2"
|
||||||
|
>Start</v-btn
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
@click.prevent=""
|
||||||
|
:disabled="opcuaStore.values[nodeIdCurrentControlMode] != 2"
|
||||||
|
>Stop</v-btn
|
||||||
|
>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useOpcuaStore } from "@/stores/opcua";
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AutomaticMode",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
requestedPower: 0,
|
||||||
|
checkReqPower: [
|
||||||
|
(value) => {
|
||||||
|
if (value <= this.maxPower && value >= this.minPower) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return `Requested power must be < ${this.maxPower} and > ${this.minPower}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
modes: [
|
||||||
|
{ title: "Auto Remote", value: 1 },
|
||||||
|
{ title: "Auto local", value: 2 },
|
||||||
|
{ title: "Safetycheck", value: 3 },
|
||||||
|
{ title: "Capacity test", value: 4 },
|
||||||
|
{ title: "Manual", value: 5 },
|
||||||
|
{ title: "Balancing", value: 6 },
|
||||||
|
{ title: "Cycling", value: 7 },
|
||||||
|
{ title: "Precharge", value: 8 },
|
||||||
|
{ title: "Doppelhöcker", value: 9 },
|
||||||
|
{ title: "Semi Auto", value: 10 },
|
||||||
|
],
|
||||||
|
selectedMode: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onEnter(event) {
|
||||||
|
event.target.active = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
nodeIdCurrentControlMode: String,
|
||||||
|
nodeIdCanChangeMode: String,
|
||||||
|
minPower: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
maxPower: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
currentControlMode() {
|
||||||
|
return this.opcuaStore.values[this.nodeIdCurrentControlMode];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.subscribe([
|
||||||
|
this.nodeIdCurrentControlMode,
|
||||||
|
this.nodeIdCanChangeMode,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
51
src/components/BaseComponents/ControlButton.vue
Normal file
51
src/components/BaseComponents/ControlButton.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<v-btn
|
||||||
|
:disabled="!this.opcuaStore.values[this.cbReleaseNodeId]"
|
||||||
|
:class="{'bg-primary': isActive}"
|
||||||
|
@click="onClick"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
import { useOpcuaStore } from "@/stores/opcua";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ControlButton",
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
cbFeedbackNodeId() {
|
||||||
|
return `${this.controlButtonBaseNodeId}.eFeedback`;
|
||||||
|
},
|
||||||
|
cbReleaseNodeId() {
|
||||||
|
return `${this.controlButtonBaseNodeId}.xRelease`;
|
||||||
|
},
|
||||||
|
cbRequestNodeId() {
|
||||||
|
return `${this.controlButtonBaseNodeId}.xRequest`;
|
||||||
|
},
|
||||||
|
isActive() {
|
||||||
|
return this.opcuaStore.values[this.cbFeedbackNodeId];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
controlButtonBaseNodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick() {
|
||||||
|
this.opcuaStore.sendCommand({
|
||||||
|
nodeId: this.cbRequestNodeId,
|
||||||
|
nodeValue: true,
|
||||||
|
nodeType: this.opcuaStore.opcuaDatatypes.Boolean,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.subscribe([this.cbFeedbackNodeId, this.cbReleaseNodeId]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
123
src/components/BaseComponents/MotorControl.vue
Normal file
123
src/components/BaseComponents/MotorControl.vue
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<template>
|
||||||
|
<v-card :title="motorName" max-width="300" w-auto>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row justify="center">
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-text-field
|
||||||
|
:rules="checkSetpoint"
|
||||||
|
:reverse="true"
|
||||||
|
:disabled="opcuaStore.values[currentControlModeNodeId] != 2"
|
||||||
|
:prefix="opcuaStore.values[spUnitNodeId]"
|
||||||
|
@focus="$event.target.select()"
|
||||||
|
@keyup.enter="$event.target.blur()"
|
||||||
|
label="Setpoint"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<h1>/ {{ processValue }}</h1>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<control-button block :control-button-base-node-id="startCBNodeId">
|
||||||
|
Start
|
||||||
|
</control-button>
|
||||||
|
</v-col>
|
||||||
|
<v-col cosl="6">
|
||||||
|
<control-button block :control-button-base-node-id="stopCBNodeId">
|
||||||
|
Stop
|
||||||
|
</control-button>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
import { useOpcuaStore } from "@/stores/opcua";
|
||||||
|
import ControlButton from "./ControlButton.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "MotorControl",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
checkSetpoint: [
|
||||||
|
(value) => {
|
||||||
|
if (
|
||||||
|
value <= this.opcuaStore.values[this.spMaxValueNodeId] &&
|
||||||
|
value >= this.opcuaStore.values[this.spMinValueNodeId]
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return `Setpoint must be <= ${this.opcuaStore.values[this.spMaxValueNodeId]} and >= ${this.opcuaStore.values[this.spMinValueNodeId]}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
startCBNodeId() {
|
||||||
|
return `${this.motorNodeId}.stStartButton`;
|
||||||
|
},
|
||||||
|
stopCBNodeId() {
|
||||||
|
return `${this.motorNodeId}.stStopButton`;
|
||||||
|
},
|
||||||
|
spNodeId() {
|
||||||
|
return `${this.motorNodeId}.stSetpoint.rValue`;
|
||||||
|
},
|
||||||
|
currentControlModeNodeId() {
|
||||||
|
return `${this.motorNodeId}.iCurrentMode`;
|
||||||
|
},
|
||||||
|
spMinValueNodeId() {
|
||||||
|
return `${this.motorNodeId}.stSetpoint.rMin`;
|
||||||
|
},
|
||||||
|
spMaxValueNodeId() {
|
||||||
|
return `${this.motorNodeId}.stSetpoint.rMax`;
|
||||||
|
},
|
||||||
|
spUnitNodeId() {
|
||||||
|
return `${this.motorNodeId}.stSetpoint.sUnit`;
|
||||||
|
},
|
||||||
|
pvUnitNodeId() {
|
||||||
|
return `${this.motorNodeId}.stProcessValue.sUnit`;
|
||||||
|
},
|
||||||
|
pvNodeId() {
|
||||||
|
return `${this.motorNodeId}.stProcessValue.rValue`;
|
||||||
|
},
|
||||||
|
processValue() {
|
||||||
|
const value = Number(this.opcuaStore.values[this.pvNodeId]).toFixed(
|
||||||
|
this.numDecimals
|
||||||
|
);
|
||||||
|
const unit = this.opcuaStore.values[this.pvUnitNodeId];
|
||||||
|
return `${value} ${unit}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ControlButton,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
motorNodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
motorName: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
numDecimals: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.subscribe([
|
||||||
|
this.currentControlModeNodeId,
|
||||||
|
this.spMinValueNodeId,
|
||||||
|
this.spMaxValueNodeId,
|
||||||
|
this.spUnitNodeId,
|
||||||
|
this.pvUnitNodeId,
|
||||||
|
this.pvNodeId,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
83
src/components/BaseComponents/ProcessValue.vue
Normal file
83
src/components/BaseComponents/ProcessValue.vue
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="mx-auto">
|
||||||
|
<v-card-item :title="this.valueTitle">
|
||||||
|
<!-- <template v-slot:subtitle>
|
||||||
|
<v-icon
|
||||||
|
class="me-1 pb-1"
|
||||||
|
color="error"
|
||||||
|
icon="mdi-alert"
|
||||||
|
size="18"
|
||||||
|
></v-icon>
|
||||||
|
|
||||||
|
Extreme Weather Alert
|
||||||
|
</template> -->
|
||||||
|
</v-card-item>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<v-row align="end" no-gutters>
|
||||||
|
<v-col class="text-h4" cols="12">
|
||||||
|
<div class="">
|
||||||
|
{{ formattedString }}
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<!-- <v-col class="text-right" cols="6">
|
||||||
|
<v-icon color="error" icon="mdi-weather-hurricane" size="88"></v-icon>
|
||||||
|
</v-col> -->
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<!-- <div class="d-flex py-3 justify-space-between">
|
||||||
|
<v-list-item density="compact" prepend-icon="mdi-weather-windy">
|
||||||
|
<v-list-item-subtitle>123 km/h</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-list-item density="compact" prepend-icon="mdi-weather-pouring">
|
||||||
|
<v-list-item-subtitle>48%</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</div> -->
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
import { useOpcuaStore } from "@/stores/opcua";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProcessValue",
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
formattedString() {
|
||||||
|
const value = Number(this.opcuaStore.values[this.nodeIdValue]).toFixed(
|
||||||
|
this.numDecimals
|
||||||
|
);
|
||||||
|
const unit = this.opcuaStore.values[this.nodeIdUnit];
|
||||||
|
return `${value} ${unit}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
numDecimals: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
valueTitle: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
nodeIdValue: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
nodeIdUnit: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.subscribe([this.nodeIdValue, this.nodeIdUnit]);
|
||||||
|
},
|
||||||
|
// unmounted() {
|
||||||
|
// console.log("Unsubscribed");
|
||||||
|
// this.opcuaStore.unsubscribe([this.nodeIdValue, this.nodeIdUnit]);
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
</script>
|
||||||
49
src/components/BaseComponents/ValveControl.vue
Normal file
49
src/components/BaseComponents/ValveControl.vue
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<v-card :title="valveName">
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<control-button block :control-button-base-node-id="openCBNodeId">
|
||||||
|
Open
|
||||||
|
</control-button>
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<control-button block :control-button-base-node-id="closeCBNodeId">
|
||||||
|
Close
|
||||||
|
</control-button>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
import { useOpcuaStore } from "@/stores/opcua";
|
||||||
|
import ControlButton from "./ControlButton.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ValveControl",
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
openCBNodeId() {
|
||||||
|
return `${this.valveNodeId}.stOpenButton`;
|
||||||
|
},
|
||||||
|
closeCBNodeId() {
|
||||||
|
return `${this.valveNodeId}.stCloseButton`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ControlButton,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
valveNodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
valveName: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
43
src/components/BatteryState.vue
Normal file
43
src/components/BatteryState.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<v-card title="Battery State">
|
||||||
|
<v-card-text>
|
||||||
|
<v-timeline align="start" side="end">
|
||||||
|
<v-timeline-item size="small" :fill-dot="currentState === 0">
|
||||||
|
<div class="d-flex">
|
||||||
|
<strong class="me-4">Off</strong>
|
||||||
|
</div>
|
||||||
|
</v-timeline-item>
|
||||||
|
|
||||||
|
<v-timeline-item size="small" :fill-dot="currentState === 1">
|
||||||
|
<div class="d-flex">
|
||||||
|
<strong class="me-4">Startup</strong>
|
||||||
|
</div>
|
||||||
|
</v-timeline-item>
|
||||||
|
|
||||||
|
<v-timeline-item size="small" :fill-dot="currentState === 2">
|
||||||
|
<div class="d-flex">
|
||||||
|
<strong class="me-4">Enabled</strong>
|
||||||
|
</div>
|
||||||
|
</v-timeline-item>
|
||||||
|
|
||||||
|
<v-timeline-item size="small" :fill-dot="currentState === 3">
|
||||||
|
<div class="d-flex">
|
||||||
|
<strong class="me-4">Shutdown</strong>
|
||||||
|
</div>
|
||||||
|
</v-timeline-item>
|
||||||
|
</v-timeline>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BatteryState",
|
||||||
|
props: {
|
||||||
|
currentState: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
37
src/components/Businesscard.vue
Normal file
37
src/components/Businesscard.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<v-card>
|
||||||
|
<v-row class="ma-0 pa-0">
|
||||||
|
<v-col cols="6" class="bg-blue">
|
||||||
|
<v-img
|
||||||
|
src="https://cdn.vuetifyjs.com/docs/images/brand-kit/v-logo-circle.png"
|
||||||
|
cover
|
||||||
|
>
|
||||||
|
</v-img>
|
||||||
|
<h1 class="text-center">Vuetify Company</h1>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6" class="d-flex flex-column justify-center align-center">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<h2>Matthias Heisig</h2>
|
||||||
|
<p>Konstrukteur</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex pt-4">
|
||||||
|
<v-icon class="mr-2">mdi-phone</v-icon>
|
||||||
|
<p>+49 1520 8545728</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex pt-4">
|
||||||
|
<v-icon class="mr-2">mdi-map-marker</v-icon>
|
||||||
|
<p>Deutschland</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex pt-4">
|
||||||
|
<v-icon class="mr-2">mdi-email</v-icon>
|
||||||
|
<p>m.heisig@heisig-gmbh.de</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
42
src/components/EventsTable.vue
Normal file
42
src/components/EventsTable.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<v-table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-left" width="100">Type</th>
|
||||||
|
<th class="text-left">Message</th>
|
||||||
|
<th class="text-left">Raised</th>
|
||||||
|
<th class="text-left">Cleared</th>
|
||||||
|
<th class="text-left">Confirmed</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<!-- <tbody>
|
||||||
|
<tr v-for="(value, index) in this.opcuaStore["opcuaEvents"]" :key="index">
|
||||||
|
<td>
|
||||||
|
<v-icon v-if="value.type === 750" color="error" icon="mdi-alert-circle"></v-icon>
|
||||||
|
<v-icon v-else="value.type === 500" color="warning" icon="mdi-alert"></v-icon>
|
||||||
|
</td>
|
||||||
|
<td>{{ value.message }}</td>
|
||||||
|
<td>{{ value.raised }}</td>
|
||||||
|
<td>{{ value.cleared }}</td>
|
||||||
|
<td>{{ value.confirmed }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody> -->
|
||||||
|
</v-table>
|
||||||
|
<p>{{ opcuaStore["opcuaEvents"] }}</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
import { useOpcuaStore } from "@/stores/opcua";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "EventTable",
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
arrayFromMap() {
|
||||||
|
const test = Array.from(this.opcuaStore["opcuaEvents"].values());
|
||||||
|
return test;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
67
src/components/ModuleSemiAuto.vue
Normal file
67
src/components/ModuleSemiAuto.vue
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<v-card width="250">
|
||||||
|
<v-card-title>Module {{ modNumber }}</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-switch
|
||||||
|
v-for="(unitTag, index) in this.unitTags"
|
||||||
|
:key="unitTag"
|
||||||
|
:model-value="this.opcuaStore.values[unitTag]"
|
||||||
|
:label="`Unit ${index+1}`"
|
||||||
|
@click.prevent="onClick(index, $event)"
|
||||||
|
></v-switch>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from "axios";
|
||||||
|
import { useOpcuaStore } from "@/stores/opcua";
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SemiAutoControl",
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.subscribe(this.unitTags);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async onSwitchChange(unit) {
|
||||||
|
let value = this.opcuaStore.values[this.unitTags[unit]]
|
||||||
|
console.log(`Unit: ${unit}`)
|
||||||
|
try {
|
||||||
|
const semiAutoData = {
|
||||||
|
string: this.strng,
|
||||||
|
module: this.modNumber,
|
||||||
|
unit: unit,
|
||||||
|
enable: !value,
|
||||||
|
};
|
||||||
|
const response = await axios.post(
|
||||||
|
"http://127.0.0.1:8000/semiAuto",
|
||||||
|
semiAutoData
|
||||||
|
);
|
||||||
|
console.log(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onClick(unitIndex, event) {
|
||||||
|
let value = this.opcuaStore.values[this.unitTags[unitIndex]]
|
||||||
|
event.target.loading="warning"
|
||||||
|
this.opcuaStore.sendCommand({"nodeId": this.unitTags[unitIndex], "nodeValue": !value, "nodeType": this.opcuaStore.opcuaDatatypes.Boolean})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modNumber: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
strng: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
unitTags: Array,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
43
src/components/ModuleView.vue
Normal file
43
src/components/ModuleView.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<v-container fluid>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<unit-control-view :unit-number="1" :unit-base-node-id="`${moduleBaseNodeId}.stHMIInterfaceUnit1`" />
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="6">
|
||||||
|
<unit-control-view :unit-number="2" :unit-base-node-id="`${moduleBaseNodeId}.stHMIInterfaceUnit2`" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<unit-control-view :unit-number="3" :unit-base-node-id="`${moduleBaseNodeId}.stHMIInterfaceUnit3`" />
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="6">
|
||||||
|
<unit-control-view :unit-number="4" :unit-base-node-id="`${moduleBaseNodeId}.stHMIInterfaceUnit4`" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
<!-- `${unitBaseNodeId}.stHMIInterfaceUnit1`-->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import UnitControlView from './UnitControlView.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ModuleView",
|
||||||
|
components: {
|
||||||
|
UnitControlView,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
moduleBaseNodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
35
src/components/README.md
Normal file
35
src/components/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Components
|
||||||
|
|
||||||
|
Vue template files in this folder are automatically imported.
|
||||||
|
|
||||||
|
## 🚀 Usage
|
||||||
|
|
||||||
|
Importing is handled by [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components). This plugin automatically imports `.vue` files created in the `src/components` directory, and registers them as global components. This means that you can use any component in your application without having to manually import it.
|
||||||
|
|
||||||
|
The following example assumes a component located at `src/components/MyComponent.vue`:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<MyComponent />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
//
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
When your template is rendered, the component's import will automatically be inlined, which renders to this:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<MyComponent />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import MyComponent from '@/components/MyComponent.vue'
|
||||||
|
</script>
|
||||||
|
```
|
||||||
63
src/components/TestTable.vue
Normal file
63
src/components/TestTable.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-4">
|
||||||
|
<h2 class="text-xl font-bold mb-4">Test Data Table</h2>
|
||||||
|
<table class="min-w-full border border-gray-300">
|
||||||
|
<thead class="bg-gray-100">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2 border">Severity</th>
|
||||||
|
<th class="px-4 py-2 border">Message</th>
|
||||||
|
<th class="px-4 py-2 border">Active State</th>
|
||||||
|
<th class="px-4 py-2 border">Confirmed State</th>
|
||||||
|
<th class="px-4 py-2 border">Retain</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="(item, index) in test_data"
|
||||||
|
:key="index"
|
||||||
|
class="text-center hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<td class="border px-4 py-2">{{ item.Severity }}</td>
|
||||||
|
<td class="border px-4 py-2">{{ item.Message }}</td>
|
||||||
|
<td class="border px-4 py-2">{{ item.ActiveState ? '✅' : '❌' }}</td>
|
||||||
|
<td class="border px-4 py-2">{{ item.ConfirmedState ? '✅' : '❌' }}</td>
|
||||||
|
<td class="border px-4 py-2">{{ item.Retain ? '✅' : '❌' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "TestDataTable",
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
test_data: [
|
||||||
|
{
|
||||||
|
Severity: 750,
|
||||||
|
Message: "Es ist ein Fehler aufgetreten",
|
||||||
|
ActiveState: true,
|
||||||
|
ConfirmedState: false,
|
||||||
|
Retain: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Severity: 500,
|
||||||
|
Message: "Es ist eine Warnung aufgetreten",
|
||||||
|
ActiveState: true,
|
||||||
|
ConfirmedState: false,
|
||||||
|
Retain: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
97
src/components/UnitControl.vue
Normal file
97
src/components/UnitControl.vue
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<v-sheet fluid class="pa-6 border-thin">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card title="Semi auto">
|
||||||
|
<!-- Semi auto enable -->
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<control-button block :controlButtonBaseNodeId="semiAutoCBEnableNodeId">Enable</control-button>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<control-button block :controlButtonBaseNodeId="semiAutoCBDisableNodeId">Disable</control-button>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<!-- Posolyt valve -->
|
||||||
|
<valve-control
|
||||||
|
valve-name="Posolyt valve"
|
||||||
|
:valve-node-id="`${this.unitBaseNodeId}.stNS12`"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<!-- Negolyt valve -->
|
||||||
|
<valve-control
|
||||||
|
valve-name="Negolyt valve"
|
||||||
|
:valve-node-id="`${this.unitBaseNodeId}.stNS22`"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<!-- Posolyt pump -->
|
||||||
|
<motor-control motor-name="Posolyt Pump" :num-decimals="0" :motor-node-id="posolytPumpNodeId"/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<!-- Negolyt pump -->
|
||||||
|
<motor-control motor-name="Negolyt Pump" :num-decimals="0" :motor-node-id="negolytPumpNodeId" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useOpcuaStore } from "@/stores/opcua";
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
import ValveControl from "./BaseComponents/ValveControl.vue";
|
||||||
|
import ControlButton from "./BaseComponents/ControlButton.vue";
|
||||||
|
import MotorControl from "./BaseComponents/MotorControl.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "UnitControl",
|
||||||
|
components: {
|
||||||
|
ValveControl,
|
||||||
|
ControlButton,
|
||||||
|
MotorControl,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
unitBaseNodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
valveUnitNodeId(unit) {
|
||||||
|
return `${this.unitBaseNodeId}.`;
|
||||||
|
},
|
||||||
|
semiAutoCBEnableNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stButtonSemiAutoEnable`;
|
||||||
|
},
|
||||||
|
semiAutoCBDisableNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stButtonSemiAutoDisable`;
|
||||||
|
},
|
||||||
|
posolytPumpNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stNS11`;
|
||||||
|
},
|
||||||
|
negolytPumpNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stNS21`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async onClick(enable) {
|
||||||
|
this.opcuaStore.sendCommand({
|
||||||
|
nodeId: this.semiAutoEnabledNodeId,
|
||||||
|
nodeValue: enable,
|
||||||
|
nodeType: this.opcuaStore.opcuaDatatypes.Boolean,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
40
src/components/UnitControlView.vue
Normal file
40
src/components/UnitControlView.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<v-sheet class="border-thin pa-6">
|
||||||
|
<v-row class="text-center">
|
||||||
|
<v-col cols="12">
|
||||||
|
<h1>Unit {{ unitNumber }}</h1>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<unit-state :unit-number="1" :unitBaseNodeId="unitBaseNodeId" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<unit-control :unit-base-node-id="unitBaseNodeId" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import UnitState from "./UnitState.vue";
|
||||||
|
import UnitControl from "./UnitControl.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "UnitcontrolView",
|
||||||
|
components: {
|
||||||
|
UnitState,
|
||||||
|
UnitControl,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
unitNumber: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
unitBaseNodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
137
src/components/UnitState.vue
Normal file
137
src/components/UnitState.vue
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<template>
|
||||||
|
<v-sheet fluid class="pa-6 border-thin" max-width="650">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<process-value
|
||||||
|
value-title="Voltage"
|
||||||
|
:node-Id-Value="voltageValueNodeId"
|
||||||
|
:node-id-unit="voltageUnitNodeId"
|
||||||
|
:num-decimals="1"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<process-value
|
||||||
|
value-title="Posolyt Inlet Pressure"
|
||||||
|
:node-Id-Value="posPresSegValueNodeId"
|
||||||
|
:node-id-unit="posPresSegUnitNodeId"
|
||||||
|
:num-decimals="0"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<process-value
|
||||||
|
value-title="Negolyt Inlet Pressure"
|
||||||
|
:node-Id-Value="negPresSegValueNodeId"
|
||||||
|
:node-id-unit="negPresSegUnitNodeId"
|
||||||
|
:num-decimals="0"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<process-value
|
||||||
|
value-title="Posolyt Tank Pressure"
|
||||||
|
:node-Id-Value="posPresTankValueNodeId"
|
||||||
|
:node-id-unit="posPresTankUnitNodeId"
|
||||||
|
:num-decimals="0"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<process-value
|
||||||
|
value-title="Negolyt Tank Pressure"
|
||||||
|
:node-Id-Value="negPresTankValueNodeId"
|
||||||
|
:node-id-unit="negPresTankUnitNodeId"
|
||||||
|
:num-decimals="0"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<process-value
|
||||||
|
value-title="Posolyt Temp"
|
||||||
|
:node-Id-Value="posTempValueNodeId"
|
||||||
|
:node-id-unit="posTempUnitNodeId"
|
||||||
|
:num-decimals="1"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<process-value
|
||||||
|
value-title="Negolyt Temp"
|
||||||
|
:node-Id-Value="negTempValueNodeId"
|
||||||
|
:node-id-unit="negTempUnitNodeId"
|
||||||
|
:num-decimals="1"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from "pinia";
|
||||||
|
import { useOpcuaStore } from "@/stores/opcua";
|
||||||
|
import ProcessValue from "./BaseComponents/ProcessValue.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "UnitState",
|
||||||
|
components: {
|
||||||
|
ProcessValue,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
voltageValueNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stE31.rValue`;
|
||||||
|
},
|
||||||
|
voltageUnitNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stE31.sUnit`;
|
||||||
|
},
|
||||||
|
posPresSegValueNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stP11.rValue`;
|
||||||
|
},
|
||||||
|
posPresSegUnitNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stP11.sUnit`;
|
||||||
|
},
|
||||||
|
negPresSegValueNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stP21.rValue`;
|
||||||
|
},
|
||||||
|
negPresSegUnitNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stP21.sUnit`;
|
||||||
|
},
|
||||||
|
posPresTankValueNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stP12.rValue`;
|
||||||
|
},
|
||||||
|
posPresTankUnitNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stP12.sUnit`;
|
||||||
|
},
|
||||||
|
negPresTankValueNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stP22.rValue`;
|
||||||
|
},
|
||||||
|
negPresTankUnitNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stP22.sUnit`;
|
||||||
|
},
|
||||||
|
posTempValueNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stT11.rValue`;
|
||||||
|
},
|
||||||
|
posTempUnitNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stT11.sUnit`;
|
||||||
|
},
|
||||||
|
negTempValueNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stT21.rValue`;
|
||||||
|
},
|
||||||
|
negTempUnitNodeId() {
|
||||||
|
return `${this.unitBaseNodeId}.stT21.sUnit`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
unitBaseNodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
unitNumber: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stE31.rValue -->
|
||||||
26
src/main.js
Normal file
26
src/main.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* main.js
|
||||||
|
*
|
||||||
|
* Bootstraps Vuetify and other plugins then mounts the App`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Plugins
|
||||||
|
import { registerPlugins } from '@/plugins'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
// Composables
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import 'unfonts.css'
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
registerPlugins(app)
|
||||||
|
|
||||||
|
app.use(pinia)
|
||||||
|
app.mount('#app')
|
||||||
3
src/plugins/README.md
Normal file
3
src/plugins/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Plugins
|
||||||
|
|
||||||
|
Plugins are a way to extend the functionality of your Vue application. Use this folder for registering plugins that you want to use globally.
|
||||||
12
src/plugins/index.js
Normal file
12
src/plugins/index.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* plugins/index.js
|
||||||
|
*
|
||||||
|
* Automatically included in `./src/main.js`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Plugins
|
||||||
|
import vuetify from './vuetify'
|
||||||
|
|
||||||
|
export function registerPlugins (app) {
|
||||||
|
app.use(vuetify)
|
||||||
|
}
|
||||||
19
src/plugins/vuetify.js
Normal file
19
src/plugins/vuetify.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* plugins/vuetify.js
|
||||||
|
*
|
||||||
|
* Framework documentation: https://vuetifyjs.com`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
import 'vuetify/styles'
|
||||||
|
|
||||||
|
// Composables
|
||||||
|
import { createVuetify } from 'vuetify'
|
||||||
|
|
||||||
|
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||||
|
export default createVuetify({
|
||||||
|
theme: {
|
||||||
|
defaultTheme: 'system',
|
||||||
|
},
|
||||||
|
})
|
||||||
169
src/stores/opcua.js
Normal file
169
src/stores/opcua.js
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import msgpack from "@ygoe/msgpack";
|
||||||
|
|
||||||
|
export const useOpcuaStore = defineStore("opcua", {
|
||||||
|
state: () => ({
|
||||||
|
values: {},
|
||||||
|
opcuaEvents: new Map(),
|
||||||
|
ws: null,
|
||||||
|
connected: false,
|
||||||
|
subscriptions: new Set(),
|
||||||
|
opcuaDatatypes: {
|
||||||
|
Null: 0,
|
||||||
|
Boolean: 1,
|
||||||
|
SByte: 2,
|
||||||
|
Byte: 3,
|
||||||
|
Int16: 4,
|
||||||
|
UInt16: 5,
|
||||||
|
Int32: 6,
|
||||||
|
UInt32: 7,
|
||||||
|
Int64: 8,
|
||||||
|
UInt64: 9,
|
||||||
|
Float: 10,
|
||||||
|
Double: 11,
|
||||||
|
String: 12,
|
||||||
|
DateTime: 13,
|
||||||
|
Guid: 14,
|
||||||
|
ByteString: 15,
|
||||||
|
XmlElement: 16,
|
||||||
|
NodeId: 17,
|
||||||
|
ExpandedNodeId: 18,
|
||||||
|
StatusCode: 19,
|
||||||
|
QualifiedName: 20,
|
||||||
|
LocalizedText: 21,
|
||||||
|
ExtensionObject: 22,
|
||||||
|
DataValue: 23,
|
||||||
|
Variant: 24,
|
||||||
|
DiagnosticInfo: 25,
|
||||||
|
},
|
||||||
|
reconnectTimeout: null,
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
connect() {
|
||||||
|
this.ws = new WebSocket("ws://localhost:8000/ws");
|
||||||
|
this.ws.binaryType = "arraybuffer";
|
||||||
|
|
||||||
|
this.ws.onopen = () => {
|
||||||
|
this.connected = true;
|
||||||
|
// Initiale Subscriptions senden
|
||||||
|
const msg = msgpack.encode({
|
||||||
|
subscribe: Array.from(this.subscriptions),
|
||||||
|
});
|
||||||
|
this.ws.send(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onmessage = (event) => {
|
||||||
|
const data = msgpack.decode(new Uint8Array(event.data));
|
||||||
|
|
||||||
|
if (data.initial) {
|
||||||
|
Object.entries(data.initial.values).forEach(([tag, val]) => {
|
||||||
|
this.values[tag] = val;
|
||||||
|
});
|
||||||
|
Object.entries(data.initial.events).forEach(([tag, val]) => {
|
||||||
|
this.opcuaEvents.set(tag, val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.u) {
|
||||||
|
Object.entries(data.u).forEach(([nodeId, val]) => {
|
||||||
|
this.values[nodeId] = val;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.e) {
|
||||||
|
console.log(data.e);
|
||||||
|
Object.entries(data.e).forEach(([nodeId, event]) => {
|
||||||
|
// Event is still relevant
|
||||||
|
if (event.Retain) {
|
||||||
|
let entry = {};
|
||||||
|
|
||||||
|
// If we already have an event with the same nodeId
|
||||||
|
if (this.opcuaEvents.has(nodeId)) {
|
||||||
|
entry = this.opcuaEventsget(nodeId);
|
||||||
|
entry.type = event.Severity;
|
||||||
|
//entry.message = event.message;
|
||||||
|
// check if it is still active
|
||||||
|
if (event.ActiveState) {
|
||||||
|
entry.raised = new Date(event.ActiveStateTransTime).toLocaleString("de-DE");
|
||||||
|
} else {
|
||||||
|
// If not, write the time to cleared
|
||||||
|
entry.cleared = new Date(event.ActiveStateTransTime).toLocaleString("de-DE");
|
||||||
|
}
|
||||||
|
if (event.ConfirmedState) {
|
||||||
|
entry.confirmed = new Date(event.ConfirmedStateTransTime).toLocaleString("de-DE");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entry = {
|
||||||
|
type: event.Severity,
|
||||||
|
message: event.Message,
|
||||||
|
raised: new Date(event.ActiveStateTransTime).toLocaleString("de-DE"),
|
||||||
|
cleared: event.ActiveState ? "" : new Date(event.ActiveStateTransTime).toLocaleString("de-DE"),
|
||||||
|
confirmed: event.ConfirmedState ? new Date(event.ConfirmedStateTransTime).toLocaleString("de-DE") : "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.opcuaEvents.set(nodeId, entry);
|
||||||
|
} else {
|
||||||
|
this.opcuaEvents.delete(nodeId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.entries(data.e).forEach(([nodeId, val]) => {
|
||||||
|
// Event is not yet cleared
|
||||||
|
// if (val.Retain)
|
||||||
|
// {
|
||||||
|
// console.log(val)
|
||||||
|
// //this.opcuaEvents.set(nodeId) = val;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// if (this.opcuaEvents.has(nodeId)) {
|
||||||
|
// this.opcuaEvents.delete(nodeId);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onclose = () => {
|
||||||
|
//this.reconnect();
|
||||||
|
this.connected = false;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
reconnect() {
|
||||||
|
if (this.reconnectTimeout) return; // Verhindert mehrfachen Reconnect
|
||||||
|
this.reconnectTimeout = setTimeout(() => {
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
this.connect();
|
||||||
|
}, 3000);
|
||||||
|
},
|
||||||
|
|
||||||
|
subscribe(tags) {
|
||||||
|
tags.forEach((tag) => this.subscriptions.add(tag));
|
||||||
|
if (this.connected && this.ws) {
|
||||||
|
const msg = msgpack.encode({ subscribe: tags });
|
||||||
|
this.ws.send(msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
unsubscribe(tags) {
|
||||||
|
tags.forEach((tag) => this.subscriptions.delete(tag));
|
||||||
|
if (this.connected && this.ws) {
|
||||||
|
const msg = msgpack.encode({ unsubscribe: tags });
|
||||||
|
this.ws.send(msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sendCommand(command) {
|
||||||
|
if (this.ws && this.connected) {
|
||||||
|
const msg = msgpack.encode({ command: command });
|
||||||
|
this.ws.send(msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue(tag) {
|
||||||
|
return this.values[tag];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
53
vite.config.mjs
Normal file
53
vite.config.mjs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Plugins
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import Vue from '@vitejs/plugin-vue'
|
||||||
|
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
||||||
|
import Fonts from 'unplugin-fonts/vite'
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
Vue({
|
||||||
|
template: { transformAssetUrls },
|
||||||
|
}),
|
||||||
|
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
|
||||||
|
Vuetify(),
|
||||||
|
Components(),
|
||||||
|
Fonts({
|
||||||
|
fontsource: {
|
||||||
|
families: [
|
||||||
|
{
|
||||||
|
name: 'Roboto',
|
||||||
|
weights: [100, 300, 400, 500, 700, 900],
|
||||||
|
styles: ['normal', 'italic'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['vuetify'],
|
||||||
|
},
|
||||||
|
define: { 'process.env': {} },
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('src', import.meta.url)),
|
||||||
|
},
|
||||||
|
extensions: [
|
||||||
|
'.js',
|
||||||
|
'.json',
|
||||||
|
'.jsx',
|
||||||
|
'.mjs',
|
||||||
|
'.ts',
|
||||||
|
'.tsx',
|
||||||
|
'.vue',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user