init report && move live_monitor_vue
This commit is contained in:
53
live_monitor_vue/src/App.vue
Normal file
53
live_monitor_vue/src/App.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script>
|
||||
import {mapGetters, mapMutations} from 'vuex'
|
||||
import MenuContainer from "@frames/Menu/MenuContainer.vue"
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
MenuContainer
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('auth', ['inited']),
|
||||
pageName() {
|
||||
return this.$route.name
|
||||
},
|
||||
outerPages () {
|
||||
return ['auth', '404']
|
||||
},
|
||||
innerPage () {
|
||||
return !this.outerPages.includes(this.pageName) && this.inited
|
||||
},
|
||||
outerPage () {
|
||||
return this.outerPages.includes(this.pageName) && this.inited
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initAuth(localStorage.getItem('token'))
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('auth', ['initAuth'])
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="innerPage"
|
||||
class="flex min-h-[100vh]"
|
||||
>
|
||||
<MenuContainer>
|
||||
<router-view />
|
||||
</MenuContainer>
|
||||
</div>
|
||||
<div
|
||||
v-if="outerPage"
|
||||
class="relative"
|
||||
>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
0
live_monitor_vue/src/adapters/AdapterOfMachines.js
Normal file
0
live_monitor_vue/src/adapters/AdapterOfMachines.js
Normal file
1
live_monitor_vue/src/assets/vue.svg
Normal file
1
live_monitor_vue/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 496 B |
53
live_monitor_vue/src/components/1_atoms/Accordion.vue
Normal file
53
live_monitor_vue/src/components/1_atoms/Accordion.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import Modal from '@molecules/Modal/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'Accordion',
|
||||
components: {
|
||||
Modal,
|
||||
},
|
||||
props: {
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
},
|
||||
setup(props, {slots}) {
|
||||
const hasHeader = slots.header != undefined
|
||||
const hasBody = slots.body != undefined
|
||||
return {
|
||||
hasHeader,
|
||||
hasBody
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div class="relative w-full">
|
||||
<input type="checkbox" :id="id" value="" :checked="isOpen" class="hidden peer" required="">
|
||||
<label :for="id" class="inline-flex items-center justify-between w-full px-5 h-[50px] text-gray-600 bg-white border border-gray-200 rounded-lg cursor-pointer dark:hover:text-gray-300 dark:border-gray-700 peer-checked:border-slate-300 peer-checked:rounded-b-none peer-checked:border-slate-200 hover:text-gray-600 dark:peer-checked:text-gray-300 peer-checked:text-gray-600 hover:bg-gray-50 dark:text-gray-400 dark:bg-gray-800 transition-all">
|
||||
<div v-if="hasHeader"><slot name="header" /></div>
|
||||
</label>
|
||||
<i class="ri-arrow-down-s-line absolute right-[10px] top-[15px] peer-checked:rotate-180 transition-all pointer-events-none"></i>
|
||||
<div v-if="hasBody" class="hidden peer-checked:block transition-all w-full px-5 py-4 text-gray-600 bg-white border border-gray-200 rounded-b-lg cursor-default">
|
||||
<slot name="body" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
53
live_monitor_vue/src/components/1_atoms/Button.vue
Normal file
53
live_monitor_vue/src/components/1_atoms/Button.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<button @click="setSelected()" :type="type" :class="classBtn" class="w-full relative col-span-4 md:col-span-2 items-center justify-center text-center border-solid border-blue-1 z-10 cursor-pointer">
|
||||
<i v-if="classIcon" :class="classIcon" class="text-blue-1"></i>
|
||||
{{ title }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
},
|
||||
props: {
|
||||
onClick: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
},
|
||||
},
|
||||
selected: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
minWidth: {
|
||||
type: String,
|
||||
default: "80px",
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
classBtn: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
classIcon: {
|
||||
type: String,
|
||||
default: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setSelected: function() {
|
||||
this.onClick(this.selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
59
live_monitor_vue/src/components/1_atoms/ButtonDiscard.vue
Normal file
59
live_monitor_vue/src/components/1_atoms/ButtonDiscard.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<fwb-button
|
||||
color="dark"
|
||||
:onclick="onClick"
|
||||
outline
|
||||
square
|
||||
>
|
||||
<svg
|
||||
class="feather feather-x-square"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
height="18"
|
||||
rx="2"
|
||||
ry="2"
|
||||
width="18"
|
||||
x="3"
|
||||
y="3"
|
||||
/>
|
||||
<line
|
||||
x1="9"
|
||||
x2="15"
|
||||
y1="9"
|
||||
y2="15"
|
||||
/>
|
||||
<line
|
||||
x1="15"
|
||||
x2="9"
|
||||
y1="9"
|
||||
y2="15"
|
||||
/>
|
||||
</svg>
|
||||
</fwb-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {FwbButton} from "flowbite-vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FwbButton
|
||||
},
|
||||
props: {
|
||||
onClick: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
41
live_monitor_vue/src/components/1_atoms/ButtonEdit.vue
Normal file
41
live_monitor_vue/src/components/1_atoms/ButtonEdit.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<fwb-button
|
||||
color="dark"
|
||||
:onclick="onClick"
|
||||
outline
|
||||
square
|
||||
>
|
||||
<svg
|
||||
class="feather feather-edit"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||
</svg>
|
||||
</fwb-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {FwbButton} from "flowbite-vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FwbButton
|
||||
},
|
||||
props: {
|
||||
onClick: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
42
live_monitor_vue/src/components/1_atoms/ButtonSave.vue
Normal file
42
live_monitor_vue/src/components/1_atoms/ButtonSave.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<fwb-button
|
||||
color="dark"
|
||||
:onclick="onClick"
|
||||
outline
|
||||
square
|
||||
>
|
||||
<svg
|
||||
class="feather feather-save"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
|
||||
<polyline points="17 21 17 13 7 13 7 21" />
|
||||
<polyline points="7 3 7 8 15 8" />
|
||||
</svg>
|
||||
</fwb-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {FwbButton} from "flowbite-vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FwbButton
|
||||
},
|
||||
props: {
|
||||
onClick: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
87
live_monitor_vue/src/components/1_atoms/DoubleSwitch.vue
Normal file
87
live_monitor_vue/src/components/1_atoms/DoubleSwitch.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
// import Button from "@/components/1_atoms/Button.vue";
|
||||
|
||||
export default {
|
||||
name: 'DoubleSwitch',
|
||||
components: {
|
||||
// Button,
|
||||
},
|
||||
props: {
|
||||
machine: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
firstTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
secondTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
firstColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
secondColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// isChecked: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
mounted () {
|
||||
|
||||
},
|
||||
inject: ['isChecked'],
|
||||
// watch: {
|
||||
// isChecked: {
|
||||
// handler(newValue, oldValue) {
|
||||
// this.$emit('switched', newValue)
|
||||
// },
|
||||
// deep: true
|
||||
// },
|
||||
// },
|
||||
emits: ['switched'],
|
||||
methods: {
|
||||
buttonClass: function(value) {
|
||||
return `border border-slate-400 flex items-center justify-center cursor-pointer rounded transition-all text-xs text-center py-[5px] px-2 ${value}`
|
||||
},
|
||||
setChecked(e) {
|
||||
// this.isChecked = e.target.checked
|
||||
this.$emit('switched', e.target.checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
|
||||
<template>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
v-bind="$attrs"
|
||||
class="input sr-only peer/checkbox"
|
||||
type="checkbox"
|
||||
:checked="isChecked"
|
||||
@change="setChecked"
|
||||
/>
|
||||
|
||||
<div :class="`w-11 h-6 rounded-full peer peer-focus:ring-4 peer-focus:ring-${secondColor}-300 dark:peer-focus:ring-${secondColor}-800 dark:bg-${firstColor}-700 peer-checked/checkbox:after:translate-x-full peer-checked/checkbox:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-${firstColor}-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-${firstColor}-600`" :style="{background: !isChecked ? firstColor : secondColor}"></div>
|
||||
<span class="block peer-checked/checkbox:hidden ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"> {{ firstTitle }}</span>
|
||||
<span class="hidden peer-checked/checkbox:block ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"> {{ secondTitle }} </span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
109
live_monitor_vue/src/components/1_atoms/OneVideo.vue
Normal file
109
live_monitor_vue/src/components/1_atoms/OneVideo.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="flex border-8 w-1/2 flex-col py-1 gap-y-5">
|
||||
<div class="flex flex-row gap-y-5 w-full justify-between">
|
||||
<fwb-p
|
||||
v-if="!isEdit"
|
||||
class="self-center px-5"
|
||||
>
|
||||
{{ label }}
|
||||
</fwb-p>
|
||||
<div
|
||||
class="flex flex-row gap-x-5 justify-between"
|
||||
:class="{'w-full': isEdit}"
|
||||
>
|
||||
<fwb-input
|
||||
v-if="isEdit"
|
||||
class="w-full"
|
||||
:model-value="realLink"
|
||||
@update:model-value="args => {tempLink = args}"
|
||||
/>
|
||||
<ButtonEdit
|
||||
v-if="!isEdit"
|
||||
:on-click="() => {isEdit = true}"
|
||||
/>
|
||||
<ButtonSave
|
||||
v-if="isEdit"
|
||||
:on-click="() => {clickSave()}"
|
||||
/>
|
||||
<ButtonDiscard
|
||||
v-if="isEdit"
|
||||
:on-click="() => {clickDiscard()}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<YouTube
|
||||
v-if="isLinkNormal && isReady"
|
||||
ref="youtube"
|
||||
:src="realLink"
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ButtonSave from "@atoms/ButtonSave.vue";
|
||||
import YouTube from "vue3-youtube";
|
||||
import ButtonEdit from "@atoms/ButtonEdit.vue";
|
||||
import ButtonDiscard from "@atoms/ButtonDiscard.vue";
|
||||
import {FwbInput, FwbP} from "flowbite-vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FwbInput,
|
||||
FwbP,
|
||||
YouTube, ButtonDiscard, ButtonSave, ButtonEdit
|
||||
},
|
||||
props: {
|
||||
videoLink: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isEdit: false,
|
||||
isReady: false,
|
||||
tempLink: this.$props.videoLink,
|
||||
realLink: this.$props.videoLink,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLinkNormal() {
|
||||
return this.$data.realLink.toString().includes("https://www.youtube.com/watch?")
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.timeout) {
|
||||
setTimeout(() => {
|
||||
this.isReady = true
|
||||
}, this.timeout);
|
||||
} else {
|
||||
this.isReady = true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateLink(newLink) {
|
||||
console.log(newLink)
|
||||
},
|
||||
clickSave() {
|
||||
this.$data.isEdit = false
|
||||
let l = this.$data.tempLink
|
||||
if (l.toString().includes("https://www.youtube.com/watch?")) {
|
||||
this.$data.realLink = l
|
||||
}
|
||||
},
|
||||
clickDiscard() {
|
||||
this.$data.isEdit = false
|
||||
console.log(`discard ${this.$data.tempLink}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,75 @@
|
||||
<!-- eslint-disable vue/prop-name-casing -->
|
||||
<script>
|
||||
import Modal from '../Modal/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'ButtonModal',
|
||||
components: {Modal},
|
||||
props: {
|
||||
text: {
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
"btnClass": {
|
||||
default: "",
|
||||
type: String,
|
||||
},
|
||||
containerClass: {
|
||||
default: "",
|
||||
type: String,
|
||||
},
|
||||
forcedBtnClass: {
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
onToggle: {
|
||||
default: () => null,
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
open: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
__btnClass__() {
|
||||
if (this.forcedBtnClass) return this.forcedBtnClass
|
||||
return `${this.btnClass} cursor-pointer w-full bg-transparent hover:bg-primary text-primary font-semibold hover:text-white py-1 text-ssm px-4 border border-primary hover:border-transparent rounded-lg text-center transition_up`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle () {
|
||||
this.open = !this.open
|
||||
this.onToggle({isOpen: this.open})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="text"
|
||||
:class="__btnClass__"
|
||||
@click="toggle"
|
||||
>
|
||||
{{ text }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!text"
|
||||
:class="__btnClass__"
|
||||
@click="toggle"
|
||||
>
|
||||
<slot name="button" />
|
||||
</div>
|
||||
<Modal
|
||||
v-if="open"
|
||||
:close="toggle"
|
||||
:isOpen="true"
|
||||
:containerClass="containerClass"
|
||||
>
|
||||
<slot />
|
||||
</Modal>
|
||||
</template>
|
||||
112
live_monitor_vue/src/components/2_molecules/Chart/index.vue
Normal file
112
live_monitor_vue/src/components/2_molecules/Chart/index.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import { toRefs } from 'vue'
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex";
|
||||
import Button from "@/components/1_atoms/Button.vue";
|
||||
import ECharts from '@store/hooks/Echarts';
|
||||
|
||||
export default {
|
||||
name: 'Chart',
|
||||
components: {
|
||||
Button,
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
sizes: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: ''
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxWidth: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxHeight: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartStyles: {
|
||||
maxWidth: `${this.maxWidth}`,
|
||||
maxHeight: `${this.maxHeight}`,
|
||||
top: '15px;',
|
||||
padding: '10px',
|
||||
margin: '10px',
|
||||
boxShadow: 'rgb(0 0 0 / 12%) 2px 3px 7px',
|
||||
border: '1px solid rgba(0, 0, 0, 0.15)',
|
||||
}
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
computed: {
|
||||
// ...mapGetters('main', ['activeChartData', 'prevGroupByData', 'activeMenuChartData']),
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick(function () {
|
||||
const chart = new ECharts(this.id, this.sizes)
|
||||
this.setTypeFunc(this.type, chart, this.data)
|
||||
chart.onClickedBarCharts(this.updateCharts)
|
||||
})
|
||||
},
|
||||
updated () {
|
||||
this.$nextTick(function () {
|
||||
const chart = new ECharts(this.id, this.sizes)
|
||||
this.setTypeFunc(this.type, chart, this.data)
|
||||
chart.onClickedBarCharts(this.updateCharts)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions('machines', ['updateCharts']),
|
||||
buttonClass: function(value) {
|
||||
return `border border-slate-400 flex items-center justify-center cursor-pointer rounded transition-all text-xs text-center py-[5px] px-2 ${value}`
|
||||
},
|
||||
setTypeFunc: function(type, chart, data) {
|
||||
switch(type) {
|
||||
case 'pie': return chart.makePieCharts({...data, type: type})
|
||||
case 'mount': return chart.makeBarMountCharts({...data, type: type, height: this.height})
|
||||
case 'mainMl': return chart.makeBarCharts({...data, type: type})
|
||||
case 'default': return chart.makeBarTypesCharts({...data, type: type})
|
||||
case 'types': return chart.makeBarTypesCharts({...data, type: type})
|
||||
case 'without_first_layout': return chart.makeBarTypesCharts({...data, type: type})
|
||||
case 'ml': return chart.makeBarTypesCharts({...data, type: type})
|
||||
case 'packLines': return chart.makeLinesPackCharts({...data, type: type})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div :id="id" class="flex grow justify-center relative overflow-hidden bg-white" :style="chartStyles">
|
||||
</div>
|
||||
<div id="button_close" class="flex absolute h-max top-[50px] right-[45px]">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<input
|
||||
ref="datepicker"
|
||||
type="text"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 flatpickr-input"
|
||||
>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import flatpickr from "flatpickr";
|
||||
import { Russian } from "flatpickr/dist/l10n/ru";
|
||||
|
||||
export default {
|
||||
name: 'Datepicker',
|
||||
components: {},
|
||||
inject: ['defaultDate'],
|
||||
props: {
|
||||
enableTime: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
dateFormat: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
onchange: {
|
||||
type: Function,
|
||||
default: () => null
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
datepicker: null,
|
||||
value: this.defaultDate
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_dateFormat () {
|
||||
if (this.dateFormat) return this.dateFormat
|
||||
if (this.enableTime) return "d.m.Y H:i"
|
||||
return "d.m.Y"
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.datepicker = flatpickr(this.$refs.datepicker, {
|
||||
allowInput: true,
|
||||
enableTime: this.enableTime,
|
||||
dateFormat: this._dateFormat,
|
||||
locale: Russian,
|
||||
onChange: (a,_b,_c) => {
|
||||
this.onchange(a[0])
|
||||
}
|
||||
});
|
||||
this.datepicker.setDate(this.defaultDate)
|
||||
|
||||
},
|
||||
updated() {
|
||||
this.$nextTick(function () {
|
||||
this.datepicker = flatpickr(this.$refs.datepicker, {
|
||||
allowInput: true,
|
||||
enableTime: this.enableTime,
|
||||
dateFormat: this._dateFormat,
|
||||
locale: Russian,
|
||||
});
|
||||
this.datepicker.setDate(this.value)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
300
live_monitor_vue/src/components/2_molecules/MapModal/helpers.js
Normal file
300
live_monitor_vue/src/components/2_molecules/MapModal/helpers.js
Normal file
@@ -0,0 +1,300 @@
|
||||
import Map from 'ol/Map.js';
|
||||
import OSM from 'ol/source/OSM.js';
|
||||
import TileLayer from 'ol/layer/Tile.js';
|
||||
import View from 'ol/View.js';
|
||||
import { transform, fromLonLat, toLonLat } from "ol/proj";
|
||||
import Point from "ol/geom/Point";
|
||||
import {Fill, Stroke, Style, Text, Icon} from 'ol/style.js';
|
||||
import { Vector as VectorLayer } from "ol/layer";
|
||||
import { Vector as VectorSource } from "ol/source";
|
||||
import Feature from "ol/Feature";
|
||||
import Overlay from "ol/Overlay";
|
||||
import {toStringHDMS} from 'ol/coordinate.js';
|
||||
|
||||
const svg = (color = "black", course) => {
|
||||
const screen_width = document.documentElement.clientWidth;
|
||||
|
||||
let svg_width = 25;
|
||||
let svg_height = 45;
|
||||
|
||||
let text_size = "30px";
|
||||
|
||||
if (
|
||||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
navigator.userAgent
|
||||
)
|
||||
) {
|
||||
svg_width = screen_width < 700 ? 30 : 50;
|
||||
}
|
||||
|
||||
return `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="rgb(59 130 246)" width="${svg_width}" height="${svg_height}" viewBox="0 0 24 24" ><path fill="none" d="M0 0h24v24H0z"/><path d="M18.364 17.364L12 23.728l-6.364-6.364a9 9 0 1 1 12.728 0zM12 15a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0-2a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></svg>
|
||||
`
|
||||
};
|
||||
|
||||
const createMap = (elId, zoom, coordinates = [37.619829, 55.752603]) => {
|
||||
return new Map(
|
||||
{
|
||||
target: elId,
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: new OSM(),
|
||||
}),
|
||||
],
|
||||
view: new View({
|
||||
projection: "EPSG:3857",
|
||||
center: fromLonLat(coordinates),
|
||||
zoom: zoom,
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const mapFeature = (coord) => {
|
||||
return new Feature({
|
||||
geometry: new Point(coord).transform("EPSG:4326", "EPSG:900913"),
|
||||
projection: "EPSG:4326",
|
||||
});
|
||||
}
|
||||
|
||||
const iconFeature = (point) => {
|
||||
return new Feature({
|
||||
geometry: new Point(point).transform("EPSG:4326", "EPSG:900913"),
|
||||
projection: "EPSG:4326",
|
||||
});
|
||||
};
|
||||
|
||||
const iconStyle = (color, text, course = null) =>
|
||||
new Style({
|
||||
image: new Icon({
|
||||
scale: 1.4,
|
||||
anchor: [0.45, 32],
|
||||
anchorXUnits: "fraction",
|
||||
anchorYUnits: "pixels",
|
||||
src: "data:image/svg+xml;utf8," + svg(color, course),
|
||||
}),
|
||||
text: new Text({
|
||||
text: text,
|
||||
offsetY: -14,
|
||||
offsetX: -18,
|
||||
textBaseline: 'middle',
|
||||
backgroundFill: new Fill({color: [90, 90, 60, 0.8]}),
|
||||
backgroundStroke: new Stroke({color: [25, 230, 115, 0]}),
|
||||
padding: [3, 3, 3, 3],
|
||||
textAlign: 'right',
|
||||
justify: 'left',
|
||||
font: '12px bold Calibri,sans-serif',
|
||||
fill: new Fill({
|
||||
color: 'white',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const labelStyle = (text) => new Style({
|
||||
text: new Text({
|
||||
text: text,
|
||||
font: '25px Calibri,sans-serif',
|
||||
fill: new Fill({
|
||||
color: '#fff',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const addLayerIcon = (map, coordinates, text) => {
|
||||
return map.addLayer(
|
||||
new VectorLayer({
|
||||
type: "vector_layer",
|
||||
source: new VectorSource({
|
||||
features: [iconFeature(coordinates)],
|
||||
}),
|
||||
style: [
|
||||
iconStyle("green", text),
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const addLayer = (map, coordinates, text) => {
|
||||
return map.addLayer(
|
||||
new VectorLayer({
|
||||
type: "vector_layer",
|
||||
source: new VectorSource({
|
||||
features: [mapFeature(coordinates), iconFeature(coordinates)],
|
||||
}),
|
||||
style: [
|
||||
iconStyle("green", text),
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const setView = (map, zoom, coordinates) => {
|
||||
return map.setView(new View({
|
||||
projection: "EPSG:3857",
|
||||
center: fromLonLat(coordinates),
|
||||
zoom: zoom,
|
||||
}))
|
||||
}
|
||||
|
||||
const addLayerWithEmptyCoords = (map, labelText) => {
|
||||
return map.addLayer(
|
||||
new VectorLayer({
|
||||
type: "vector_layer",
|
||||
background: 'rgba(0,0,0,0.7)',
|
||||
source: new VectorSource({
|
||||
features: [mapFeature([37.619829, 55.752603])]
|
||||
}),
|
||||
style: [labelStyle(labelText)]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const removeLayers = (map) => {
|
||||
return map.getLayers().forEach(layer => {
|
||||
if(layer?.values_?.type == "vector_layer") {
|
||||
map.removeLayer(layer)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const setTooltip = (map, coordinates, machine, tooltip_id, content_id, closer_id) => {
|
||||
const tooltipSelector = document.getElementById(tooltip_id)
|
||||
const contentSelector = document.getElementById(content_id)
|
||||
const onClose = document.getElementById(closer_id)
|
||||
|
||||
const tooltip = new Overlay({
|
||||
element: tooltipSelector,
|
||||
})
|
||||
map.addOverlay(tooltip)
|
||||
|
||||
const tooltipContent = new Overlay({
|
||||
position: coordinates,
|
||||
positioning: 'center-center',
|
||||
element: contentSelector,
|
||||
stopEvent: false,
|
||||
});
|
||||
|
||||
map.addOverlay(tooltipContent)
|
||||
|
||||
|
||||
const onClosed = () => {
|
||||
if(onClose) {
|
||||
tooltip.setPosition(undefined)
|
||||
onClose.blur()
|
||||
const tooltipText = tooltipSelector.querySelector(`.${content_id}`)
|
||||
if(tooltipText) {
|
||||
tooltipText.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map.on('click', function (e) {
|
||||
const feature = map.forEachFeatureAtPixel(e.pixel, function (feature) {
|
||||
return feature;
|
||||
})
|
||||
|
||||
onClosed()
|
||||
|
||||
const tooltipText = document.createElement('div')
|
||||
tooltipText.classList.add(content_id)
|
||||
|
||||
if (feature) {
|
||||
const container = tooltip.getElement();
|
||||
const props = feature.getProperties()
|
||||
|
||||
const coordsFormated = feature.getGeometry()
|
||||
const coordsFormat = feature.getGeometry().getFlatCoordinates()
|
||||
|
||||
// const hdms = toLonLat(coordinates)
|
||||
|
||||
// setTimeout(() => {
|
||||
container.style.opacity = "1"
|
||||
container.style.display = "flex"
|
||||
container.style.minWidth = "max-content"
|
||||
tooltipText.innerHTML = `
|
||||
<div class="flex flex-col justify-items-center">
|
||||
<div class="tooltip-title flex text-lg text-blue-500"> ${machine.machine_type} </div>
|
||||
<code> ${coordinates} </code>
|
||||
<hr class="mt-1 mb-2">
|
||||
<div class="tooltip-title flex text-sm"><div class="w-20">Imei:</div> <div>${machine.imei}</div> </div>
|
||||
<div class="tooltip-title flex text-sm"><div class="w-20">Дорога:</div> <div>${machine.railway_name}</div> </div>
|
||||
<div class="tooltip-title flex text-sm"><div class="w-20">Компания:</div> <div>${machine.org_name}</div> </div>
|
||||
<div class="tooltip-title flex text-sm"><div class="w-20">8зн номер:</div> <div>${machine.nomer_zn8}</div> </div>
|
||||
<div class="tooltip-title flex text-sm"><div class="w-20">Устройство:</div> <div>${machine.device_number}</div> </div>
|
||||
<a class="flex justify-center items-center bg-blue-300 hover:bg-blue-400 text-gray-800 p-1 mt-2 rounded" target="_blank" href="/html/askr_devices/analyze_comm/${machine.imei}"}">Детализация</a>
|
||||
</div>`
|
||||
container.prepend(tooltipText)
|
||||
// }, 200)
|
||||
|
||||
tooltip.setPosition(e.coordinate)
|
||||
}
|
||||
});
|
||||
|
||||
map.on('pointermove', function (e) {
|
||||
const pixel = map.getEventPixel(e.originalEvent);
|
||||
const hit = map.hasFeatureAtPixel(pixel);
|
||||
// console.log('hit', hit)
|
||||
// console.log('pixel', pixel)
|
||||
// console.log('map', map)
|
||||
// map.getTarget().style.cursor = hit ? 'pointer' : '';
|
||||
})
|
||||
|
||||
onClose.addEventListener('click', onClosed)
|
||||
}
|
||||
|
||||
|
||||
export class CoordsMap {
|
||||
|
||||
constructor(id) {
|
||||
this.id = id
|
||||
}
|
||||
|
||||
loadMap(coordinates, machine) {
|
||||
if(!coordinates) {
|
||||
if(!this.map) {
|
||||
console.log('coordinates', coordinates)
|
||||
console.log('machine', machine)
|
||||
this.map = createMap(this.id, 5)
|
||||
addLayerWithEmptyCoords(this.map, 'Данных о местоположении машины нет')
|
||||
} else {
|
||||
removeLayers(this.map)
|
||||
addLayerWithEmptyCoords(this.map, 'Данных о местоположении машины нет')
|
||||
setView(this.map, 5, [37.619829, 55.752603])
|
||||
}
|
||||
}
|
||||
|
||||
if(coordinates) {
|
||||
console.log('coordinates', coordinates)
|
||||
console.log('machine', machine)
|
||||
if(!this.map) {
|
||||
this.map = createMap(this.id, 8, coordinates)
|
||||
addLayerIcon(this.map, coordinates, `${coordinates[1] ? +coordinates[1].toFixed(5) : ''} ${coordinates[0] ? +coordinates[0].toFixed(5) : ''}`)
|
||||
|
||||
} else {
|
||||
removeLayers(this.map)
|
||||
addLayerIcon(this.map, coordinates, `${coordinates[1] ? +coordinates[1].toFixed(5) : ''} ${coordinates[0] ? +coordinates[0].toFixed(5) : ''}`)
|
||||
setView(this.map, 8, coordinates)
|
||||
}
|
||||
}
|
||||
|
||||
if(coordinates && machine) {
|
||||
if(!this.map) {
|
||||
this.map = createMap(this.id, 8, coordinates)
|
||||
addLayerIcon(this.map, coordinates, machine.machine_type)
|
||||
setTooltip(this.map, coordinates, machine, 'popup', 'popup-content', 'popup-closer')
|
||||
// this.map.addOverlay(popup)
|
||||
// this.map.addOverlay(marker(coordinates))
|
||||
// setOverlay(this.map)
|
||||
|
||||
} else if (machine) {
|
||||
removeLayers(this.map)
|
||||
addLayer(this.map, coordinates, machine.machine_type)
|
||||
setView(this.map, 8, coordinates)
|
||||
setTooltip(this.map, coordinates, machine, 'popup', 'popup-content', 'popup-closer')
|
||||
// this.map.addOverlay(popup)
|
||||
// this.map.addOverlay(marker(coordinates))
|
||||
// setOverlay(this.map)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import Modal from '@molecules/Modal/index.vue'
|
||||
import {CoordsMap} from './helpers'
|
||||
|
||||
export default {
|
||||
name: 'MapModal',
|
||||
components: {
|
||||
Modal,
|
||||
},
|
||||
props: {
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
close: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
headerClass: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
updated () {
|
||||
this.$nextTick(function () {
|
||||
if (this.data && this.id && this.open) {
|
||||
const newMap = new CoordsMap(this.id)
|
||||
newMap.loadMap(this.data.coords, this.data.machine)
|
||||
}
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-if="open"
|
||||
:close="close"
|
||||
:isOpen="true"
|
||||
:containerClass="containerClass"
|
||||
>
|
||||
|
||||
<div :class="headerClass">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<slot />
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
87
live_monitor_vue/src/components/2_molecules/Modal/index.vue
Normal file
87
live_monitor_vue/src/components/2_molecules/Modal/index.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="absolute"
|
||||
>
|
||||
<div class="absolute">
|
||||
<div
|
||||
id="select_machines_modal"
|
||||
class="fixed z-[10000] inset-0"
|
||||
>
|
||||
<!-- Modal container -->
|
||||
<div class="h-screen flex items-center justify-center p-4">
|
||||
<!-- Overlay -->
|
||||
<div
|
||||
class="absolute z-0 inset-0 bg-gray-500 opacity-75"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<!-- Modal box -->
|
||||
<div
|
||||
class="relative max-h-full overflow-y-auto bg-white rounded-lg shadow-xl "
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="0"
|
||||
autofocus=""
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-2 right-2 text-gray-400 flex space-x-1 items-center"
|
||||
aria_label="close modal"
|
||||
@click="close"
|
||||
>
|
||||
<span class="text-sm">(esc)</span>
|
||||
<i class="ri ri-close-line text-2xl" />
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="min-w-[350px] min-h-[400px] py"
|
||||
:class="containerClass"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Modal',
|
||||
components: {},
|
||||
props: {
|
||||
isOpen: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
close: {
|
||||
default: () => null,
|
||||
type: Function
|
||||
},
|
||||
containerClass: {
|
||||
default: "",
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
mounted () {
|
||||
document.addEventListener("keydown", this.onEsc);
|
||||
},
|
||||
unmounted() {
|
||||
document.removeEventListener("keydown", this.onEsc);
|
||||
},
|
||||
methods: {
|
||||
onEsc (e) {
|
||||
if (e.key === "Escape") {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
:width="size"
|
||||
:height="size"
|
||||
class="text-gray-200 animate-spin dark:text-gray-600 fill-blue-600"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'RawSpinner',
|
||||
components: {},
|
||||
props: {
|
||||
size: {
|
||||
type: Number,
|
||||
default: 25
|
||||
},
|
||||
},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="w-fit flex items-center">
|
||||
<RawSpinner
|
||||
class="mr-2"
|
||||
/>
|
||||
<span
|
||||
v-if="!disableText"
|
||||
class="text-lg text-slate-700"
|
||||
>{{ text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RawSpinner from './Spinner.vue'
|
||||
|
||||
export default {
|
||||
name: 'Spinner',
|
||||
components: {RawSpinner},
|
||||
props: {
|
||||
text: {
|
||||
default: "Загрузка",
|
||||
type: String
|
||||
},
|
||||
disableText: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,184 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex"
|
||||
import { validation } from './helper'
|
||||
import { Alert } from "@store/hooks/Alert"
|
||||
|
||||
export default {
|
||||
name: 'Pagination',
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputValue: null,
|
||||
initLocalStorage: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
watch: {
|
||||
params: {
|
||||
handler(value, oldValue) {
|
||||
if(value) {
|
||||
const loader = document.querySelector(`#table_loder_${this.params.id}`)
|
||||
loader.style.display = 'none'
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
|
||||
},
|
||||
methods: {
|
||||
selectOptions: (selectOption, paginationOptions) => {
|
||||
const options = selectOption ? selectOption : optionsDefault
|
||||
const currentOptions = options.filter((el) => {
|
||||
if(el <= Math.ceil(paginationOptions.total / 10) * 10) {
|
||||
return el
|
||||
}
|
||||
})
|
||||
if(paginationOptions.show_all) {
|
||||
currentOptions.push('Все')
|
||||
}
|
||||
return currentOptions
|
||||
},
|
||||
currentEntries: (pagination) => {
|
||||
if (pagination) {
|
||||
const endPageDataIdx = pagination.page_size * pagination.page_number
|
||||
const isEndPageDataIdxMore = endPageDataIdx > pagination.total
|
||||
if(isEndPageDataIdxMore || pagination.page_size > pagination.total) {
|
||||
return (`${pagination.page_size * (pagination.page_number - 1) + 1} ... ${pagination.total}`)
|
||||
} else {
|
||||
return (`${pagination.page_size * (pagination.page_number - 1) + 1} ... ${pagination.page_size * pagination.page_number}`)
|
||||
}
|
||||
}
|
||||
},
|
||||
setPagination: function ({pageNumber, pageSize}) {
|
||||
const loader = document.querySelector(`#table_loder_${this.params.id}`)
|
||||
loader.style.display = 'block'
|
||||
this.params.updatePagination({pageNumber: pageNumber, pageSize: pageSize})
|
||||
},
|
||||
updateSelectedValue: function (e) {
|
||||
const loader = document.querySelector(`#table_loder_${this.params.id}`)
|
||||
loader.style.display = 'block'
|
||||
this.setPagination({
|
||||
pageNumber: 1, // this.params.paginationOptions.page_number,
|
||||
pageSize: e.target.value
|
||||
})
|
||||
},
|
||||
updateInputValue: function (e) {
|
||||
const pageNumber = e.target.value.trim()
|
||||
const buttonPageInput = document.querySelector(`.page-button-${this.params.id}`)
|
||||
if(pageNumber) {
|
||||
this.inputValue = pageNumber
|
||||
buttonPageInput.removeAttribute('disabled')
|
||||
buttonPageInput.classList.remove('opacity-50')
|
||||
} else {
|
||||
buttonPageInput.setAttribute('disabled', 'disabled')
|
||||
buttonPageInput.classList.add('opacity-50')
|
||||
}
|
||||
},
|
||||
setPaginationButton: function (e) {
|
||||
if(this.inputValue) {
|
||||
const error = validation(this.inputValue, this.params.paginationOptions)
|
||||
if(Number(this.inputValue) === this.params.paginationOptions.page_number) {
|
||||
return false
|
||||
}
|
||||
if(!error) {
|
||||
e.target.removeAttribute('disabled')
|
||||
e.target.classList.remove('opacity-50')
|
||||
const loader = document.querySelector(`#table_loder_${this.params.id}`)
|
||||
loader.style.display = 'block'
|
||||
e.target.focus()
|
||||
const params = {pageNumber: this.inputValue, pageSize: this.params.paginationOptions.page_size}
|
||||
return this.params.updatePagination(params)
|
||||
} else {
|
||||
e.target.setAttribute('disabled', 'disabled')
|
||||
e.target.classList.add('opacity-50')
|
||||
const errorObj = {
|
||||
id: this.params.id,
|
||||
color: "red",
|
||||
icon: "<i class='ri-alert-line'></i>",
|
||||
message: error,
|
||||
delay: 5000,
|
||||
}
|
||||
return new Alert(errorObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div class="pagination w-full flex flex-row items-center justify-between gap-3 mt-2 mb-8 xl:mb-0 pb-8 xl:p-2.5 bg-slate-100 border-2 border-solid border-slate-300 rounded-lg flex-col 2xl:flex-row">
|
||||
<div class="pagination-left flex flex-row gap-1">
|
||||
<div class="current-entries flex text-sm font-bold">Показаны {{currentEntries(params?.paginationOptions)}}</div>
|
||||
<div class="count-entries flex text-sm font-bold">(Всего записей - {{params?.paginationOptions?.total}})</div>
|
||||
</div>
|
||||
<div class="pagination-right flex flex-col xl:flex-row items-center gap-3">
|
||||
<div :id="`table_loder_${params.id}`" class="hidden w-full h-auto bg-light flex items-center justify-center px-2"><div type-node="loader" class="flex my_margin-3 items-center justify-center">
|
||||
<div role="status">
|
||||
<svg aria-hidden="true" class="mr-2 w-6 h-6 text-gray-400 animate-spin dark:text-gray-400 fill-white" viewBox="0 0 100 101" fill="none" >
|
||||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
||||
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div></div>
|
||||
<div class="buttons flex h-fit gap-1 sm:gap-3 text-sm">
|
||||
<div class="flex gap-1 sm:gap-3" v-if="params.paginationOptions.page_number > 1">
|
||||
<div v-if="params.paginationOptions.page_number - 1 !== 1">
|
||||
<button :table_btn="`${params.id}`" class="previous flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: 1, pageSize: params.paginationOptions.page_size})">
|
||||
<span>1</span>
|
||||
</button>
|
||||
</div>
|
||||
<button :table_btn="`${params.id}`" class="previous flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: params.paginationOptions.page_number - 1, pageSize: params.paginationOptions.page_size})">
|
||||
<span class="hidden sm:flex">Назад</span><i class="ri-arrow-left-line sm:hidden"></i>
|
||||
</button>
|
||||
<button :table_btn="`${params.id}`" class="previous flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: params.paginationOptions.page_number - 1, pageSize: params.paginationOptions.page_size})">
|
||||
<span>{{params.paginationOptions.page_number - 1}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button :table_btn="`${params.id}`" class="active flex items-center text-blue-700 font-bold bg-white py-1 px-3 gap-2 rounded cursor-default">
|
||||
<span>{{params.paginationOptions.page_number}}</span>
|
||||
</button>
|
||||
<div class="flex gap-1 sm:gap-3" v-if="params.paginationOptions.page_number < params.paginationOptions.total_pages">
|
||||
<button :table_btn="`${params.id}`" class="next flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: params.paginationOptions.page_number + 1, pageSize: params.paginationOptions.page_size})">
|
||||
<span>{{params.paginationOptions.page_number + 1}}</span>
|
||||
</button>
|
||||
<button :table_btn="`${params.id}`" class="next flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: params.paginationOptions.page_number + 1, pageSize: params.paginationOptions.page_size})">
|
||||
<span class="hidden sm:flex">Вперёд</span><i class="ri-arrow-right-line sm:hidden"></i>
|
||||
</button>
|
||||
<div v-if="params.paginationOptions.page_number + 1 !== params.paginationOptions.total_pages">
|
||||
<button :table_btn="`${params.id}`" class="next flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: params.paginationOptions.total_pages, pageSize: params.paginationOptions.page_size})">
|
||||
<span>{{params.paginationOptions.total_pages}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row items-center justify-center gap-2"><input v-on:input="updateInputValue" :class="`page-input-${params.id}`" class="flex max-w-[80px] items-center text-slate-700 text-sm font-bold focus-visible:outline-blue-300 bg-white py-1 px-2 gap-2 rounded" value="" placeholder="Страница"/><button @click="setPaginationButton" disabled :class="`page-button-${params.id}`" class="opacity-50 flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-1 rounded">Перейти</button></div>
|
||||
<div id="select-on-page" class="select-on-page flex items-center justify-center gap-3 p-1 text-sm text-slate-700 font-bold">
|
||||
Показать
|
||||
<select name="on_page" v-bind:value="params.paginationOptions.page_size" v-on:change="updateSelectedValue" :table_option="`${params.id}`" class="flex text-gray-700 pl-3 pr-9 py-0.5 rounded cursor-pointer border-none" style="--tw-ring-color:focus: none" >
|
||||
<option :key={idx} v-for="(el, idx) in selectOptions(params.selectOption, params.paginationOptions)" :value="`${el === 'Все' ? params.paginationOptions.total : el}`">
|
||||
{{ el }}
|
||||
</option>`
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
356
live_monitor_vue/src/components/2_molecules/Tabulator/helper.js
Normal file
356
live_monitor_vue/src/components/2_molecules/Tabulator/helper.js
Normal file
@@ -0,0 +1,356 @@
|
||||
import { always, cond, equals, is, T, mergeLeft } from "ramda";
|
||||
import Handlebars from "handlebars/dist/cjs/handlebars";
|
||||
import moment from "moment";
|
||||
import presetConfig from './presetConfig'
|
||||
import {parseFunc, parseFunc3} from './stringFunction'
|
||||
|
||||
const tablefy = (list = []) => {
|
||||
const defaultData = (title) => ({
|
||||
headerFilter: "input",
|
||||
minWidth: 80,
|
||||
headerFilterPlaceholder: title,
|
||||
download: false,
|
||||
print: false,
|
||||
vertAlign: "middle",
|
||||
sorter: "string",
|
||||
responsive: 0
|
||||
})
|
||||
|
||||
if (!is(Array, list)) return []
|
||||
return list.map((el) => mergeLeft(el, defaultData(el?.title || "")))
|
||||
}
|
||||
|
||||
const math = (value, operator, operand, options) => {
|
||||
var left = parseFloat(value);
|
||||
var right = parseFloat(operand);
|
||||
|
||||
switch(operator) {
|
||||
case "+":
|
||||
return left + right;
|
||||
case "-":
|
||||
return left - right;
|
||||
case "*":
|
||||
return left * right;
|
||||
case "/":
|
||||
return left / right;
|
||||
case "%":
|
||||
return left % right;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const link = (col) => ({
|
||||
...col,
|
||||
formatter: (cell, { prefix = "", text, key = "id" }, onRendered) => {
|
||||
onRendered(function () {
|
||||
let row = cell.getRow().getData();
|
||||
if (!text && !cell.getValue()) {
|
||||
return null;
|
||||
}
|
||||
if (!row[key]) return null;
|
||||
|
||||
cell.getElement().innerHTML = `<a target="_blank" href="${prefix}/${
|
||||
row[key]
|
||||
}">${text || cell.getValue()}</a>`;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
[
|
||||
['renderFunc', (strFunc, record, strDataIndex) => {
|
||||
const pFunc = strFunc.replace(/##/gm, "'")
|
||||
return parseFunc(pFunc)(record[strDataIndex], record)
|
||||
}],
|
||||
['math', math],
|
||||
['presetConfig', (record, pack) => presetConfig(pack, record)],
|
||||
[
|
||||
"dt_shift",
|
||||
(date, shift, param) => {
|
||||
return date ? moment(date).add(shift, param) : "нд";
|
||||
},
|
||||
],
|
||||
[
|
||||
"dt_format",
|
||||
(date) => {
|
||||
return date ? moment(date).format("DD.MM.YYYY HH:mm") : "нд";
|
||||
},
|
||||
],
|
||||
[
|
||||
"dt_db_format",
|
||||
(date) => {
|
||||
return date ? moment(date).format("YYYY-MM-DDTHH:mm:ss") : "нд";
|
||||
},
|
||||
],
|
||||
[
|
||||
"from_now",
|
||||
(date) => {
|
||||
return moment(date).fromNow()
|
||||
},
|
||||
],
|
||||
[
|
||||
"json_parse",
|
||||
(item) => {
|
||||
return `
|
||||
<div>
|
||||
${item}
|
||||
</div>
|
||||
`
|
||||
},
|
||||
],
|
||||
[
|
||||
"time_from_sec",
|
||||
(ms) => {
|
||||
if (!ms) return "н/д"
|
||||
return `${moment(moment().subtract(Math.round(ms), 'milliseconds')).fromNow()}`.replace("назад", '')
|
||||
},
|
||||
],
|
||||
[
|
||||
"lenght",
|
||||
(list) => {
|
||||
return is(Array, list) ? list.length : 0;
|
||||
},
|
||||
],
|
||||
[
|
||||
"more",
|
||||
(one, two) => {
|
||||
return one > two;
|
||||
},
|
||||
],
|
||||
[
|
||||
"less",
|
||||
(one, two) => {
|
||||
return one < two;
|
||||
},
|
||||
],
|
||||
[
|
||||
"or",
|
||||
(one, two) => {
|
||||
return one || two;
|
||||
},
|
||||
],
|
||||
[
|
||||
"eq",
|
||||
(one, two) => {
|
||||
return one == two;
|
||||
},
|
||||
],
|
||||
[
|
||||
"from_list",
|
||||
(list, param_string) => {
|
||||
|
||||
}
|
||||
],
|
||||
[
|
||||
"endOf",
|
||||
(date, param) => moment(date).endOf(param)
|
||||
],
|
||||
[
|
||||
"startOf",
|
||||
(date, param) => moment(date).startOf(param)
|
||||
],
|
||||
[
|
||||
"render",
|
||||
(list, key) => {
|
||||
return list.map(el => el[key]).join(", ")
|
||||
}
|
||||
],
|
||||
[
|
||||
"get",
|
||||
(map, key) => {
|
||||
return map[key] ? `${map[key]}` : ""
|
||||
}
|
||||
],
|
||||
[
|
||||
"get_from",
|
||||
(map, key) => {
|
||||
return map[key] ? `${map[key]}` : ""
|
||||
}
|
||||
],
|
||||
[
|
||||
"uint8parse",
|
||||
(list) => pipe(
|
||||
(x) => Uint8Array.from(x, e => e.charCodeAt(0)),
|
||||
Array.from
|
||||
)(list)
|
||||
],
|
||||
[
|
||||
"filter_aoo",
|
||||
(list = [], key, _availible_list) => {
|
||||
const availible_list = pipe(
|
||||
(x) => Uint8Array.from(x, e => e.charCodeAt(0)),
|
||||
Array.from
|
||||
)(_availible_list)
|
||||
return list.filter((el) =>
|
||||
availible_list.includes(el[key]))
|
||||
}
|
||||
]
|
||||
].forEach(([name, func]) => {
|
||||
Handlebars.registerHelper(name, func);
|
||||
});
|
||||
|
||||
const render = (col) => ({
|
||||
...col,
|
||||
formatter: (cell, _, onRendered) => {
|
||||
|
||||
onRendered(function () {
|
||||
const record = cell.getRow().getData()
|
||||
// console.log('col.onCell', parseFunc(col.onCell))
|
||||
|
||||
if(col.onCell && record.status) {
|
||||
// cell.getElement().style.backgroundColor = setColorCellFromStatus(record.status)
|
||||
cell.getElement().style.backgroundColor = parseFunc3(col.onCell)(record)
|
||||
}
|
||||
if(col.onCell && !record.status) {
|
||||
console.log('onCell', col.onCell)
|
||||
cell.getElement().style.backgroundColor = parseFunc(col.onCell)(record)?.style?.backgroundColor
|
||||
}
|
||||
if (`${col.render}`.includes("{{") && `${col.render}`.includes("}}")) {
|
||||
let record = cell.getRow().getData();
|
||||
cell.getElement().innerHTML = Handlebars.compile(col.render)(
|
||||
{...record, __record__: record}
|
||||
);
|
||||
} else {
|
||||
cell.getElement().innerHTML = parseFunc(col.render)(record[col["dataIndex"]] || record[col["field"]] || record[col["key"]], record)
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
let sorter = (a, b) => {
|
||||
let [a_field, b_field] = [a["field"], b["field"]];
|
||||
if (a_field.includes("id") && b_field.includes("id"))
|
||||
return a_field.localeCompare(b_field);
|
||||
if (a_field.includes("id")) return -1;
|
||||
if (b_field.includes("id")) return 1;
|
||||
if (a_field.includes("dt") && b_field.includes("dt"))
|
||||
return b_field.localeCompare(a_field);
|
||||
if (a_field.includes("dt")) return -1;
|
||||
if (b_field.includes("dt")) return 1;
|
||||
if (a_field.includes("imei") && b_field.includes("imei"))
|
||||
return b_field.localeCompare(a_field);
|
||||
if (a_field.includes("imei")) return -1;
|
||||
if (b_field.includes("imei")) return 1;
|
||||
return a_field.localeCompare(b_field);
|
||||
};
|
||||
|
||||
const prepCol = (col) =>
|
||||
cond([
|
||||
[() => is(String, col.render), () => render(col)],
|
||||
[equals("link"), () => link(col)],
|
||||
[T, always(col)],
|
||||
])(col.mode);
|
||||
|
||||
const prepColumns = (cols) =>
|
||||
is(Array, cols) ? cols.map((col) => prepCol(col)) : [];
|
||||
|
||||
const renderGroupHeader = ({ groupHeader, value, count, group, extra }) => {
|
||||
const group_data = group._group;
|
||||
|
||||
if (group_data.level == 0) {
|
||||
const {company_id, param, train_info: train, date: _date} = group_data.groupList[0].rows[0].data
|
||||
|
||||
const date = `${_date}`.replace(/\s(.*)/gm, "")
|
||||
|
||||
const icident_group = extra.find((el) =>
|
||||
el.date === date &&
|
||||
el.company_id == company_id &&
|
||||
el.train == train &&
|
||||
el.param == param
|
||||
) || {}
|
||||
|
||||
return Handlebars.compile(groupHeader)({
|
||||
_value: value,
|
||||
_count: count,
|
||||
extra: extra,
|
||||
icident_group: icident_group
|
||||
});
|
||||
}
|
||||
|
||||
return `${value} (${count} записей)`;
|
||||
};
|
||||
|
||||
const groupByFunction = (groupBy) => (data) =>
|
||||
Handlebars.compile(groupBy)(data);
|
||||
|
||||
const makedFiltering = (id, filterParams, selectedFilter, clearFilters) => {
|
||||
|
||||
const table = document.querySelector(`#${id}`)
|
||||
const isFiltering = document.querySelector(`${id}-filters`)
|
||||
|
||||
if(id && filterParams) {
|
||||
filterParams.unshift([])
|
||||
const filtersContainer = document.createElement('div')
|
||||
filtersContainer.setAttribute("id", `${id}-filters`)
|
||||
filtersContainer.classList.add("col-span-12")
|
||||
filtersContainer.innerHTML = `
|
||||
<div id="${id}-filtersContainer" class="filters flex flex-row items-center justify-start gap-3 mb-0 pt-2.5 bg-slate-100 rounded-t-md flex-row 2xl:flex-row">
|
||||
<div id="${id}-selectFilters" class="flex items-center justify-start gap-3 p-1 ml-2 text-base text-slate-700 font-bold">
|
||||
Фильтр по статусу
|
||||
<select name="selectFilter" id="${id}-selectFilter" class="flex text-gray-700 pl-3 pr-9 py-0.5 rounded cursor-pointer border-none" style="--tw-ring-color:focus: none" >
|
||||
${filterParams.map((el) => {
|
||||
return (`<option value='${el}'>
|
||||
${el}
|
||||
</option>`)
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<button id="${id}-filtersClear"class="w-fit h-fit flex items-center text-white text-sm font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-1 rounded">Очистить фильтр</button>
|
||||
</div>`
|
||||
if(!isFiltering) {
|
||||
table.before(filtersContainer)
|
||||
}
|
||||
else {
|
||||
isFiltering.remove()
|
||||
table.before(filtersContainer)
|
||||
}
|
||||
|
||||
const selectFilters = document.querySelectorAll(`#${id}-selectFilter`)
|
||||
const selectOptions = document.querySelectorAll(`#${id}-selectFilter option`)
|
||||
const buttonClearFilters = document.querySelector(`#${id}-filtersClear`)
|
||||
|
||||
selectFilters.forEach((el) => {
|
||||
el.onchange = (e) => {
|
||||
if(e.target.value) {
|
||||
selectOptions[0].removeAttribute('selected')
|
||||
console.log('e', e)
|
||||
console.log('selectFilters[0]', selectFilters[0])
|
||||
console.log('selectOptions[0][0]', selectOptions[0])
|
||||
selectedFilter(e.target.value)
|
||||
} else {
|
||||
clearFilters()
|
||||
e.target.setAttribute('selected', 'selected')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
buttonClearFilters.onclick = () => {
|
||||
clearFilters()
|
||||
selectOptions[0].setAttribute('selected', 'selected')
|
||||
console.log('selectFilters[0]', selectFilters[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validation = (value, pagination) => {
|
||||
const pipe = (...fns) => (...x) => fns.reduce((error, validator) => error || validator(...x), undefined)
|
||||
const required = value => {
|
||||
return value ? undefined : "*"
|
||||
}
|
||||
const minPage = length => value => {
|
||||
value = value.replace(/\s+/g, '')
|
||||
return value >= length ? undefined : `Номер страницы должен быть больше или равен ${length}`
|
||||
}
|
||||
const maxPage = length => value => {
|
||||
value = value.replace(/\s+/g, '')
|
||||
return length >= value ? undefined : `Номер страницы должен быть меньше или равен ${length}`
|
||||
}
|
||||
const onlyDigits = value => {
|
||||
const pattern = /^([\d])*$/g
|
||||
return pattern.test(value) ? undefined : `Номер страницы должен содержать только цифры`
|
||||
}
|
||||
const composedValidators = pipe(onlyDigits, minPage(1), maxPage(pagination.total_pages))
|
||||
return composedValidators(value)
|
||||
}
|
||||
|
||||
export {prepColumns, sorter, renderGroupHeader, groupByFunction, makedFiltering, tablefy, validation};
|
||||
194
live_monitor_vue/src/components/2_molecules/Tabulator/index.vue
Normal file
194
live_monitor_vue/src/components/2_molecules/Tabulator/index.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<script>
|
||||
import {TabulatorFull as Tabulator} from 'tabulator-tables';
|
||||
import {
|
||||
prepColumns,
|
||||
sorter,
|
||||
renderGroupHeader,
|
||||
groupByFunction,
|
||||
makedFiltering,
|
||||
tablefy
|
||||
} from "./helper";
|
||||
import localization from "./localization";
|
||||
import { cond, equals, is, mergeAll, sort, T, mergeDeepRight } from "ramda";
|
||||
import { pipe } from "@helpers/functions";
|
||||
import Pagination from "./Pagination.vue";
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Pagination
|
||||
},
|
||||
props: {
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
initialSort: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
groupBy: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
groupByTemplate: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
groupValues: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "550px"
|
||||
},
|
||||
groupExtraValues: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
groupHeader: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
selectOption: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
filterData: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
withTablefy: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
refId: Math.random().toString(36).slice(4),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
data () {
|
||||
if (!is(Array, this.dataSource)) return []
|
||||
if (this.withTablefy) return tablefy(this.dataSource)
|
||||
return this.dataSource
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.makeTabele()
|
||||
},
|
||||
updated() {
|
||||
this.makeTabele()
|
||||
},
|
||||
methods: {
|
||||
makeTabele () {
|
||||
this.table = new Tabulator(this.$refs[this.refId], this.getConfig())
|
||||
},
|
||||
getConfig () {
|
||||
let configurator = pipe(this.__defaultCfg());
|
||||
return configurator(
|
||||
(x) => mergeDeepRight(x, this.__getColsCfg()),
|
||||
(x) => mergeDeepRight(x, this.__getSortCfg()),
|
||||
(x) => mergeDeepRight(x, this.__getGroupCfg()),
|
||||
(x) => mergeDeepRight(x, this.__getGroupHeaderCfg()),
|
||||
// (x) => mergeAll(x, this.__getPaginationCgs())
|
||||
);
|
||||
},
|
||||
__defaultCfg () {
|
||||
return {
|
||||
locale: "ru",
|
||||
langs: localization(),
|
||||
height: this.height || "250px",
|
||||
responsiveLayout: "collapse",
|
||||
placeholder: "Нет записей",
|
||||
movableColumns: true,
|
||||
autoResize: false,
|
||||
data: this.data,
|
||||
groupStartOpen: false,
|
||||
|
||||
groupHeader: function (value, count, data, group) {
|
||||
return value + "<span>(" + count + " записей)</span>";
|
||||
},
|
||||
};
|
||||
},
|
||||
__getColsCfg() {
|
||||
let layout, columns = []
|
||||
if (this.autoColumns && is(Object, this.data[0])) {
|
||||
let first = this.data[0];
|
||||
let keys = Object.keys(first).map((key) => ({
|
||||
headerFilter: "input",
|
||||
headerFilterPlaceholder: key,
|
||||
title: key,
|
||||
field: key,
|
||||
download: false,
|
||||
print: false,
|
||||
vertAlign: "middle",
|
||||
sorter: "string",
|
||||
responsive: 0,
|
||||
minWidth: 80,
|
||||
maxWidth: 300,
|
||||
}));
|
||||
layout = "fitDataFill"
|
||||
columns = sort(sorter, keys)
|
||||
} else {
|
||||
layout = "fitDataStretch"
|
||||
columns = prepColumns(this.columns)
|
||||
}
|
||||
if (this.withTablefy) {
|
||||
columns = tablefy(columns)
|
||||
}
|
||||
return {columns, layout}
|
||||
},
|
||||
__getSortCfg() {
|
||||
if (this.initialSort) return { initialSort: this.initialSort };
|
||||
return {};
|
||||
},
|
||||
__getGroupCfg() {
|
||||
if (this.groupByTemplate)
|
||||
return { groupBy: groupByFunction(this.groupByTemplate) };
|
||||
if (this.groupBy) return { groupBy: this.groupBy };
|
||||
return {};
|
||||
},
|
||||
__getGroupHeaderCfg() {
|
||||
if (this.groupHeader)
|
||||
return {
|
||||
groupHeader: (value, count, data, group) =>
|
||||
renderGroupHeader({
|
||||
extra: this.groupExtraValues,
|
||||
groupHeader: this.groupHeader,
|
||||
value,
|
||||
count,
|
||||
data,
|
||||
group,
|
||||
}),
|
||||
};
|
||||
return {};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-4">
|
||||
<div class="overflow-x-auto w-full">
|
||||
<div
|
||||
:ref="refId"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="pagination">
|
||||
<Pagination :params="pagination"/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,29 @@
|
||||
const localization = () => ({
|
||||
ru: {
|
||||
groups: {
|
||||
item: "запись",
|
||||
items: "записей",
|
||||
},
|
||||
"pagination":{
|
||||
"page_size":"Размер таблицы", //label for the page size select element
|
||||
"page_title":"Показать страницу",//tooltip text for the numeric page button, appears in front of the page number (eg. "Show Page" will result in a tool tip of "Show Page 1" on the page 1 button)
|
||||
"first":"К первой", //text for the first page button
|
||||
"first_title":"Показать страницу", //tooltip text for the first page button
|
||||
"last":"К последней",
|
||||
"last_title":"Последняя страница",
|
||||
"prev":"<<",
|
||||
"prev_title":"<<",
|
||||
"next":">>",
|
||||
"next_title":">>",
|
||||
"all":"Все",
|
||||
"counter":{
|
||||
"showing": "Показать",
|
||||
"of": "из",
|
||||
"rows": "строки",
|
||||
"pages": "страницы",
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default localization;
|
||||
@@ -0,0 +1,244 @@
|
||||
import { cond, T } from "ramda";
|
||||
|
||||
const text = `
|
||||
0 Конфигурация не установлена (по умолчанию)
|
||||
1 Стандартная конфигурация МРТ (МПТ/АВФ)
|
||||
2 Стандартная конфигурация ЖДСМ
|
||||
3 Стандартная конфигурация УК/ВПО
|
||||
4 ЩОМ/РМ (Акселерометр)
|
||||
5 СМ (Сибирский ПГУПС)
|
||||
6 СМ (зарезервировано)
|
||||
7 Эмуляция платы БКП
|
||||
8 Конфигурация Блок-М
|
||||
40 Эмуляторы
|
||||
41 Эмуляторы (CAN1 + CAN2 (Блок-М) + ModBus
|
||||
50 Стандартная конфигурация ModBus Master (опрос внешних устройств)
|
||||
51 Стандартная конфигурация ModBus Master (опрос плат АС КРСПС)
|
||||
52 Конфигурация трекера (электрички и т.п.)
|
||||
53 Конфигурация трекер + счетчик импульсов + акселерометр
|
||||
54 РадарП
|
||||
55 СДРГО
|
||||
100 ПМА-1М РПМ
|
||||
101 ЩОМ-1400 РПМ
|
||||
102 АМ-140 СКРТ РПМ
|
||||
103 АС-01 РПМ
|
||||
104 ТЭС ПСС-1К РПМ
|
||||
105 ПСС-2П РПМ
|
||||
200 РПБ-01 ВНИКТИ
|
||||
201 МПК Спецлоко
|
||||
202 УК 25/25 ВНИКТИ
|
||||
203 СЗ-800 ВНИКТИ
|
||||
300 ЩОМ-1200С ТЖДМ
|
||||
301 ЩОМ-2000 ТЖДМc
|
||||
302 ССГС-1 ТЖДМ
|
||||
303 ВПО-С ТЖДМ
|
||||
304 МПВ ТЖДМ
|
||||
305 УПК ТЖДМ
|
||||
306 ПРЛ-М ТЖДМ
|
||||
307 РУ-700 ТЖДМ
|
||||
308 МПВ Секция 2 ТЖДМ
|
||||
1911 Конфигурация для отладки
|
||||
4096 Настраиваемая конфигурация
|
||||
`
|
||||
|
||||
//TODO SERGEY
|
||||
const parse = () => {
|
||||
text
|
||||
return {}
|
||||
}
|
||||
|
||||
const get_f1_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_2_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация, не установлена (по умолчанию)",
|
||||
1: "Стандартная, конфигурация МРТ (МПТ/АВФ)",
|
||||
2: "Стандартная конфигурация ЖДСМ",
|
||||
3: "Стандартная конфигурация УК/ВПО",
|
||||
4: "ЩОМ/РМ (Акселерометр)",
|
||||
5: "СМ (Сибирский ПГУПС)",
|
||||
6: "СМ (зарезервировано)",
|
||||
7: "Эмуляция платы БКП",
|
||||
8: "Конфигурация Блок-М",
|
||||
40: "Эмуляторы",
|
||||
41: "Эмуляторы (CAN1 + CAN2 (Блок-М) + ModBus",
|
||||
50: "Стандартная конфигурация ModBus Master (опрос внешних устройств)",
|
||||
51: "Стандартная конфигурация ModBus Master (опрос плат АС КРСПС)",
|
||||
52: "Конфигурация трекера (электрички и т.п.)",
|
||||
53: "Конфигурация трекер + счетчик импульсов + акселерометр",
|
||||
54: "РадарП",
|
||||
55: "СДРГО",
|
||||
100 : "ПМА-1М РПМ",
|
||||
101 : "ЩОМ-1400 РПМ",
|
||||
102 : "АМ-140 СКРТ РПМ",
|
||||
103 : "АС-01 РПМ",
|
||||
104 : "ТЭС ПСС-1К РПМ",
|
||||
105 : "ПСС-2П РПМ",
|
||||
200 : "РПБ-01 ВНИКТИ",
|
||||
201 : "МПК Спецлоко",
|
||||
202 : "УК 25/25 ВНИКТИ",
|
||||
203 : "СЗ-800 ВНИКТИ",
|
||||
300 : "ЩОМ-1200С ТЖДМ",
|
||||
301 : "ЩОМ-2000 ТЖДМ",
|
||||
302 : "ССГС-1 ТЖДМ",
|
||||
303 : "ВПО-С ТЖДМ",
|
||||
304 : "МПВ ТЖДМ",
|
||||
305 : "УПК ТЖДМ",
|
||||
306 : "ПРЛ-М ТЖДМ",
|
||||
307 : "РУ-700 ТЖДМ",
|
||||
308 : "МПВ Секция 2 ТЖДМ",
|
||||
1911: "Конфигурация для отладки",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_4_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Стандартная конфигурация МРТ (МПТ/АВФ)",
|
||||
2: "Стандартная конфигурация ЖДСМ",
|
||||
3: "Стандартная конфигурация УК/ВПО",
|
||||
4: "ЩОМ/РМ (Акселерометр)",
|
||||
5: "СМ (Сибирский ПГУПС)",
|
||||
6: "СМ (зарезервировано)",
|
||||
7: "Эмуляция платы БКП",
|
||||
8: "Конфигурация Блок-М",
|
||||
40: "Эмуляторы (CAN1 + ОНК-160 + ModBus)",
|
||||
41: "Эмуляторы (CAN1 + CAN2 (Блок-М) + ModBus)",
|
||||
50: "Стандартная конфигурация ModBus Master (опрос внешних устройств)",
|
||||
51: "Стандартная конфигурация ModBus Master (опрос плат АС КРСПС)",
|
||||
52: "Конфигурация трекера (электрички и т.п.)",
|
||||
53: "Конфигурация трекер + счетчик импульсов + акселерометр",
|
||||
54: "РадарП",
|
||||
55: "СДРГО",
|
||||
100: "ПМА-1М РПМ",
|
||||
101: "ЩОМ-1400 РПМ",
|
||||
102: "АМ-140 СКРТ РПМ",
|
||||
103: "АС-01 РПМ",
|
||||
104: "ТЭС ПСС-1К РПМ",
|
||||
105: "ПСС-2П РПМ",
|
||||
200: "РПБ-01 ВНИКТИ",
|
||||
201: "МПК Спецлоко",
|
||||
202: "УК 25/25 ВНИКТИ",
|
||||
203: "СЗ-800 ВНИКТИ",
|
||||
300: "ЩОМ-1200С ТЖДМ",
|
||||
301: "ЩОМ-2000 ТЖДМ",
|
||||
302: "ССГС-1 ТЖДМ",
|
||||
303: "ВПО-С ТЖДМ",
|
||||
304: "МПВ ТЖДМ",
|
||||
305: "УПК ТЖДМ",
|
||||
306: "ПРЛ-М ТЖДМ",
|
||||
307: "РУ-700 ТЖДМ",
|
||||
308: "МПВ Секция 2 ТЖДМ",
|
||||
1911: "Конфигурация для отладки",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_skrt_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Обработка импульсной СКРТ",
|
||||
2: "Конфигурация ModBus Slave Base",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const uk_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "УК AC с подключением по SPI",
|
||||
2: "УК AC с подключением по LORA",
|
||||
3: "УК DC с подключением по SPI",
|
||||
4: "УК DC с подключением по LORA",
|
||||
5: "ВПО AC/DC с подключением по SPI",
|
||||
6: "ВПО AC/DC с подключением по LORA",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_64_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_dig_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Конфигурация с эмуляторами",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_69_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_80_preset = (el) => {
|
||||
const r = {
|
||||
0: "Плата с экраном для СМ",
|
||||
1: "Плата с экраном для ПСС",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_81_preset = (el) => {
|
||||
const r = {
|
||||
0: "Плата с ультразвуковыми датчиками (СМ и т.п.)",
|
||||
4096: "Плата с ультразвуковыми датчиками (СМ и т.п.) настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_82_preset = (el) => {
|
||||
const r = {
|
||||
0: "Плата с дискретными входами + LORA (СМ Tail)",
|
||||
4096: "Плата с дискретными входами + LORA (СМ Tail) настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
|
||||
const sadko_83_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Конфигурация для МПТ",
|
||||
2: "Конфигурация для ТЭС ПСС-1К",
|
||||
3: "Конфигурация для СМ (СМ-2Б)",
|
||||
4: "Конфигурация для Динамик",
|
||||
5: "Конфигурация для УТМ-5",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
|
||||
const presetConfig = (el, record) => {
|
||||
const { board_id: board_id } = record;
|
||||
board_id == 64 && console.log(record)
|
||||
|
||||
|
||||
return cond([
|
||||
[(board) => board == 1, () => get_f1_preset(el)],
|
||||
[(board) => board == 2, () => sadko_2_preset(el)],
|
||||
[(board) => board == 4, () => sadko_4_preset(el)],
|
||||
[(board) => board == 16, () => sadko_skrt_preset(el)],
|
||||
[(board) => board == 19, () => uk_preset(el)],
|
||||
[(board) => board == 64, () => sadko_64_preset(el)],
|
||||
[(board) => board == 68, () => sadko_dig_preset(el)],
|
||||
[(board) => board == 69, () => sadko_69_preset(el)],
|
||||
[(board) => board == 80, () => sadko_80_preset(el)],
|
||||
[(board) => board == 81, () => sadko_81_preset(el)],
|
||||
[(board) => board == 82, () => sadko_82_preset(el)],
|
||||
[(board) => board == 83, () => sadko_83_preset(el)],
|
||||
[T, () => el],
|
||||
])(board_id);
|
||||
};
|
||||
|
||||
export default presetConfig
|
||||
@@ -0,0 +1,406 @@
|
||||
// import "antd/dist/antd.css";
|
||||
import axios from "axios";
|
||||
import { parseSepareteDate } from "./time_funcs";
|
||||
import {
|
||||
range,
|
||||
groupBy,
|
||||
find,
|
||||
propEq,
|
||||
findIndex,
|
||||
update,
|
||||
is,
|
||||
cond,
|
||||
T,
|
||||
isNil
|
||||
} from "ramda";
|
||||
|
||||
let getPackSettings = (userId, page, packStructId, func) => {
|
||||
if (userId && page && packStructId) {
|
||||
axios({
|
||||
method: "GET",
|
||||
url: `/api/settings/${userId}/${page}/${packStructId}`,
|
||||
headers: {
|
||||
Authorization: `vniizht ${btoa(JSON.stringify({ date: new Date() }))}`,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
func(data)
|
||||
})
|
||||
.catch((e) => console.warn(e));
|
||||
}
|
||||
};
|
||||
|
||||
const prepDiscretes = (from, to, arr) =>
|
||||
range(from, to + 1).map((el) => ({
|
||||
title: el,
|
||||
dataIndex: `disc${el}`,
|
||||
key: `disc${el}`,
|
||||
width: 60,
|
||||
render: (v) =>
|
||||
v ? (
|
||||
"вкл"
|
||||
) : (
|
||||
"выкл"
|
||||
),
|
||||
}));
|
||||
|
||||
const is_number = (val) => {
|
||||
try {
|
||||
return +val;
|
||||
} catch (_e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const is_dt = (val) => {
|
||||
let is_invalid = new Date(val) !== "Invalid Date";
|
||||
return val && val != "" && !is_invalid;
|
||||
};
|
||||
|
||||
const compare = (val, from, untill) => {
|
||||
if (is_number(val)) {
|
||||
return val >= +from && val <= +untill;
|
||||
}
|
||||
if (is_dt(val)) {
|
||||
let val = new Date(val).getTime;
|
||||
let from = new Date(from).getTime;
|
||||
let untill = new Date(untill).getTime;
|
||||
|
||||
return val >= from && val <= untill;
|
||||
}
|
||||
|
||||
return (
|
||||
`${val}`.toLowerCase() == `${from}`.toLowerCase() ||
|
||||
`${val}`.toLowerCase() == `${untill}`.toLowerCase()
|
||||
);
|
||||
};
|
||||
|
||||
const withCustomSettings = (_columns, requestSettings) => {
|
||||
let columns = _columns
|
||||
|
||||
if (requestSettings?.colors?.isOn) {
|
||||
}
|
||||
|
||||
if (requestSettings?.colors?.isOn) {
|
||||
let colors = requestSettings.colors.list.filter((el) => el.isOn);
|
||||
colors = groupBy((c) => c.param)(colors);
|
||||
coloredParams = Object.keys(colors);
|
||||
columns = columns.map((el) => {
|
||||
if (coloredParams.includes(el.dataIndex)) {
|
||||
return {
|
||||
...el,
|
||||
onCell: (data, index) => {
|
||||
let colrSettings = colors[el.dataIndex];
|
||||
let value = data[el.dataIndex];
|
||||
let color = find(({ from, untill }) =>
|
||||
compare(value, from, untill)
|
||||
)(colrSettings);
|
||||
return {
|
||||
style: { background: color?.color?.hex || "white" },
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return el;
|
||||
});
|
||||
}
|
||||
|
||||
let sort_order =
|
||||
requestSettings?.settings?.sort_order == "desc" ? "descend" : "ascend";
|
||||
let prim_param = requestSettings?.settings?.date_param;
|
||||
let prim_base_date =
|
||||
find(propEq("dataIndex", prim_param), columns) ||
|
||||
find(propEq("dataIndex", "pack_dt_new"), columns);
|
||||
let prim_index = findIndex(propEq("dataIndex", prim_param), columns);
|
||||
// сделано, потому что в пакете может быть подменена дата
|
||||
if (prim_index < 0) {
|
||||
prim_base_date = find(propEq("dataIndex", "pack_dt_new"), columns);
|
||||
prim_index = findIndex(propEq("dataIndex", "pack_dt_new"), columns);
|
||||
}
|
||||
if (prim_index >= 0) {
|
||||
let sec_param = prim_param == "pack_dt" ? "dt" : "pack_dt";
|
||||
let sec_base_date = find(propEq("dataIndex", sec_param), columns);
|
||||
let sec_index = findIndex(propEq("dataIndex", sec_param), columns);
|
||||
if (sec_index < 0) {
|
||||
sec_base_date = find(propEq("dataIndex", "pack_dt_new"), columns);
|
||||
sec_index = findIndex(propEq("dataIndex", "pack_dt_new"), columns);
|
||||
}
|
||||
if (sec_index > 0) {
|
||||
columns = update(
|
||||
prim_index,
|
||||
{
|
||||
...prim_base_date,
|
||||
defaultSortOrder: sort_order,
|
||||
sorter: (a, b) => new Date(a[prim_param]) - new Date(b[prim_param]),
|
||||
},
|
||||
columns
|
||||
);
|
||||
|
||||
let sec_object = {
|
||||
...sec_base_date,
|
||||
sorter: (a, b) => new Date(a[prim_param]) - new Date(b[prim_param]),
|
||||
};
|
||||
delete sec_object.defaultSortOrder;
|
||||
|
||||
columns = update(sec_index, sec_object, columns);
|
||||
}
|
||||
}
|
||||
|
||||
return columns;
|
||||
};
|
||||
|
||||
const fetch_sorter_field = (sorter, req_params) => {
|
||||
const date_param = req_params?.settings?.date_param
|
||||
return cond([
|
||||
[() => sorter?.field, () => sorter.field],
|
||||
[is(Array), field_from_arr(sorter, date_param)],
|
||||
[isNil, () => date_param],
|
||||
])(sorter)
|
||||
}
|
||||
|
||||
const field_from_arr = (sorter, date_param) => () => {
|
||||
const field = sorter.find((el) => `${el?.field}`.includes("dt"))?.field
|
||||
return field || date_param
|
||||
}
|
||||
|
||||
|
||||
const get_f1_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_2_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация, не установлена (по умолчанию)",
|
||||
1: "Стандартная, конфигурация МРТ (МПТ/АВФ)",
|
||||
2: "Стандартная конфигурация ЖДСМ",
|
||||
3: "Стандартная конфигурация УК/ВПО",
|
||||
4: "ЩОМ/РМ (Акселерометр)",
|
||||
5: "СМ (Сибирский ПГУПС)",
|
||||
6: "СМ (зарезервировано)",
|
||||
7: "Эмуляция платы БКП",
|
||||
8: "Конфигурация Блок-М",
|
||||
40: "Эмуляторы",
|
||||
41: "Эмуляторы (CAN1 + CAN2 (Блок-М) + ModBus",
|
||||
50: "Стандартная конфигурация ModBus Master (опрос внешних устройств)",
|
||||
51: "Стандартная конфигурация ModBus Master (опрос плат АС КРСПС)",
|
||||
52: "Конфигурация трекера (электрички и т.п.)",
|
||||
53: "Конфигурация трекер + счетчик импульсов + акселерометр",
|
||||
54: "РадарП",
|
||||
55: "СДРГО",
|
||||
100 : "ПМА-1М РПМ",
|
||||
101 : "ЩОМ-1400 РПМ",
|
||||
102 : "АМ-140 СКРТ РПМ",
|
||||
103 : "АС-01 РПМ",
|
||||
104 : "ТЭС ПСС-1К РПМ",
|
||||
105 : "ПСС-2П РПМ",
|
||||
200 : "РПБ-01 ВНИКТИ",
|
||||
201 : "МПК Спецлоко",
|
||||
202 : "УК 25/25 ВНИКТИ",
|
||||
203 : "СЗ-800 ВНИКТИ",
|
||||
300 : "ЩОМ-1200С ТЖДМ",
|
||||
301 : "ЩОМ-2000 ТЖДМ",
|
||||
302 : "ССГС-1 ТЖДМ",
|
||||
303 : "ВПО-С ТЖДМ",
|
||||
304 : "МПВ ТЖДМ",
|
||||
305 : "УПК ТЖДМ",
|
||||
306 : "ПРЛ-М ТЖДМ",
|
||||
307 : "РУ-700 ТЖДМ",
|
||||
308 : "МПВ Секция 2 ТЖДМ",
|
||||
1911: "Конфигурация для отладки",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_4_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Стандартная конфигурация МРТ (МПТ/АВФ)",
|
||||
2: "Стандартная конфигурация ЖДСМ",
|
||||
3: "Стандартная конфигурация УК/ВПО",
|
||||
4: "ЩОМ/РМ (Акселерометр)",
|
||||
5: "СМ (Сибирский ПГУПС)",
|
||||
6: "СМ (зарезервировано)",
|
||||
7: "Эмуляция платы БКП",
|
||||
8: "Конфигурация Блок-М",
|
||||
40: "Эмуляторы (CAN1 + ОНК-160 + ModBus)",
|
||||
41: "Эмуляторы (CAN1 + CAN2 (Блок-М) + ModBus)",
|
||||
50: "Стандартная конфигурация ModBus Master (опрос внешних устройств)",
|
||||
51: "Стандартная конфигурация ModBus Master (опрос плат АС КРСПС)",
|
||||
52: "Конфигурация трекера (электрички и т.п.)",
|
||||
53: "Конфигурация трекер + счетчик импульсов + акселерометр",
|
||||
54: "РадарП",
|
||||
55: "СДРГО",
|
||||
100: "ПМА-1М РПМ",
|
||||
101: "ЩОМ-1400 РПМ",
|
||||
102: "АМ-140 СКРТ РПМ",
|
||||
103: "АС-01 РПМ",
|
||||
104: "ТЭС ПСС-1К РПМ",
|
||||
105: "ПСС-2П РПМ",
|
||||
200: "РПБ-01 ВНИКТИ",
|
||||
201: "МПК Спецлоко",
|
||||
202: "УК 25/25 ВНИКТИ",
|
||||
203: "СЗ-800 ВНИКТИ",
|
||||
300: "ЩОМ-1200С ТЖДМ",
|
||||
301: "ЩОМ-2000 ТЖДМ",
|
||||
302: "ССГС-1 ТЖДМ",
|
||||
303: "ВПО-С ТЖДМ",
|
||||
304: "МПВ ТЖДМ",
|
||||
305: "УПК ТЖДМ",
|
||||
306: "ПРЛ-М ТЖДМ",
|
||||
307: "РУ-700 ТЖДМ",
|
||||
308: "МПВ Секция 2 ТЖДМ",
|
||||
1911: "Конфигурация для отладки",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_skrt_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Обработка импульсной СКРТ",
|
||||
2: "Конфигурация ModBus Slave Base",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const uk_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "УК AC с подключением по SPI",
|
||||
2: "УК AC с подключением по LORA",
|
||||
3: "УК DC с подключением по SPI",
|
||||
4: "УК DC с подключением по LORA",
|
||||
5: "ВПО AC/DC с подключением по SPI",
|
||||
6: "ВПО AC/DC с подключением по LORA",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_64_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_dig_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Конфигурация с эмуляторами",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_69_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_80_preset = (el) => {
|
||||
const r = {
|
||||
0: "Плата с экраном для СМ",
|
||||
1: "Плата с экраном для ПСС",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_81_preset = (el) => {
|
||||
const r = {
|
||||
0: "Плата с ультразвуковыми датчиками (СМ и т.п.)",
|
||||
4096: "Плата с ультразвуковыми датчиками (СМ и т.п.) настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_82_preset = (el) => {
|
||||
const r = {
|
||||
0: "Плата с дискретными входами + LORA (СМ Tail)",
|
||||
4096: "Плата с дискретными входами + LORA (СМ Tail) настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
|
||||
const sadko_83_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Конфигурация для МПТ",
|
||||
2: "Конфигурация для ТЭС ПСС-1К",
|
||||
3: "Конфигурация для СМ (СМ-2Б)",
|
||||
4: "Конфигурация для Динамик",
|
||||
5: "Конфигурация для УТМ-5",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
|
||||
const presetConfig = (el, record) => {
|
||||
const { board_id: board_id } = record;
|
||||
board_id == 64 && console.log(record)
|
||||
|
||||
|
||||
return cond([
|
||||
[(board) => board == 1, () => get_f1_preset(el)],
|
||||
[(board) => board == 2, () => sadko_2_preset(el)],
|
||||
[(board) => board == 4, () => sadko_4_preset(el)],
|
||||
[(board) => board == 16, () => sadko_skrt_preset(el)],
|
||||
[(board) => board == 19, () => uk_preset(el)],
|
||||
[(board) => board == 64, () => sadko_64_preset(el)],
|
||||
[(board) => board == 68, () => sadko_dig_preset(el)],
|
||||
[(board) => board == 69, () => sadko_69_preset(el)],
|
||||
[(board) => board == 80, () => sadko_80_preset(el)],
|
||||
[(board) => board == 81, () => sadko_81_preset(el)],
|
||||
[(board) => board == 82, () => sadko_82_preset(el)],
|
||||
[(board) => board == 83, () => sadko_83_preset(el)],
|
||||
[T, () => el],
|
||||
])(board_id);
|
||||
};
|
||||
|
||||
const fVarsNames = [
|
||||
"getPackSettings",
|
||||
"prepDiscretes",
|
||||
"is_number",
|
||||
"is_dt",
|
||||
"compare",
|
||||
"withCustomSettings",
|
||||
"parseSepareteDate",
|
||||
"presetConfig",
|
||||
];
|
||||
|
||||
const fVarsFuncs = [
|
||||
getPackSettings,
|
||||
prepDiscretes,
|
||||
is_number,
|
||||
is_dt,
|
||||
compare,
|
||||
withCustomSettings,
|
||||
parseSepareteDate,
|
||||
presetConfig,
|
||||
];
|
||||
|
||||
let parseFunc = (string) => {
|
||||
return (item, el) => {
|
||||
const f = new Function(
|
||||
"item",
|
||||
"el",
|
||||
...fVarsNames,
|
||||
`return (${string})(item, el)`
|
||||
);
|
||||
return f(item, el, ...fVarsFuncs);
|
||||
};
|
||||
};
|
||||
|
||||
let parseFunc2 = (string) => {
|
||||
const f = new Function(...fVarsNames, `return (() => ${string})()`);
|
||||
return f(...fVarsFuncs);
|
||||
};
|
||||
|
||||
const parseFunc3 = (string) => {
|
||||
const f = new Function(`return (${string})`)
|
||||
return f()
|
||||
}
|
||||
|
||||
export {parseFunc, parseFunc2, parseFunc3}
|
||||
@@ -0,0 +1,168 @@
|
||||
import { inc, type, cond, equals, T, pipe } from "ramda";
|
||||
|
||||
export const isDate = (date) =>
|
||||
type(date) === "Date" || new Date(date).getTime() > 0;
|
||||
export const formateDateToString = (date) =>
|
||||
isDate(date) && date.toLocaleString();
|
||||
|
||||
const monthNames = [
|
||||
"Январь",
|
||||
"Февраль",
|
||||
"Март",
|
||||
"Апрель",
|
||||
"Май",
|
||||
"Июнь",
|
||||
"Июль",
|
||||
"Август",
|
||||
"Сентябрь",
|
||||
"Октябрь",
|
||||
"Ноябрь",
|
||||
"Декабрь",
|
||||
];
|
||||
|
||||
export const reformateDateString = (date, format = "full") =>
|
||||
!!date ? reformate(date, format) : null;
|
||||
|
||||
const reformate = (date, format) =>
|
||||
cond([
|
||||
[
|
||||
equals("time-only"),
|
||||
() => {
|
||||
const { hr, min, sec } = getTime(date);
|
||||
return `${hr}:${min}:${sec}`;
|
||||
},
|
||||
],
|
||||
[
|
||||
equals("date-only"),
|
||||
() => {
|
||||
const { day, mth, year } = getDate(date);
|
||||
return `${day}/${mth}/${year}`;
|
||||
},
|
||||
],
|
||||
[
|
||||
equals("date-report"),
|
||||
() => {
|
||||
const { day, mth, year } = getDate(date);
|
||||
return `${year}-${mth}-${day}`;
|
||||
},
|
||||
],
|
||||
[
|
||||
equals("mth-date-report"),
|
||||
() => {
|
||||
date = new Date(date);
|
||||
return `${getMthName(date)} ${date.getDate()}`;
|
||||
},
|
||||
],
|
||||
[
|
||||
T,
|
||||
() => {
|
||||
const { hr, min, sec } = getTime(date);
|
||||
const { day, mth, year } = getDate(date);
|
||||
return `${day}/${mth}/${year} ${hr}:${min}:${sec}`;
|
||||
},
|
||||
],
|
||||
])(format);
|
||||
|
||||
export const addHours = (oldDate, h) => {
|
||||
const date = new Date(oldDate);
|
||||
date.setHours(date.getHours() + h);
|
||||
return date;
|
||||
};
|
||||
|
||||
export const lastDay = addHours(new Date(), -24);
|
||||
|
||||
const getTime = (date) => {
|
||||
date = new Date(date);
|
||||
return {
|
||||
hr: formatTime(date.getHours()),
|
||||
minutes: formatTime(date.getMinutes()),
|
||||
sec: formatTime(date.getSeconds()),
|
||||
};
|
||||
};
|
||||
|
||||
const getDate = (date) => {
|
||||
date = new Date(date);
|
||||
return {
|
||||
mth: formatDate(date.getMonth()),
|
||||
day: date.getDate(),
|
||||
year: date.getFullYear(),
|
||||
};
|
||||
};
|
||||
|
||||
export const getMthName = (date) => monthNames[date.getMonth()];
|
||||
export const formatDate = (t) => (inc(t) >= 10 ? inc(t) : `0${inc(t)}`);
|
||||
export const formatTime = (t) => `0${t}`.slice(-2);
|
||||
|
||||
export const getDateByType = (date) => {
|
||||
if (type(date) === "String") return new Date(date.replace(/\s/, "T"));
|
||||
return new Date(date);
|
||||
};
|
||||
|
||||
export const parseSepareteDate = (
|
||||
date = new Date(),
|
||||
symbol = ".",
|
||||
mode = "DateDMY"
|
||||
) => {
|
||||
const selfDate = getDateByType(date);
|
||||
const dateBits = {
|
||||
year: selfDate.getFullYear(),
|
||||
month: selfDate.getMonth() + 1,
|
||||
day: selfDate.getDate(),
|
||||
hours: selfDate.getHours(),
|
||||
minutes: selfDate.getMinutes(),
|
||||
seconds: selfDate.getSeconds(),
|
||||
};
|
||||
Object.keys(dateBits).forEach((key) => {
|
||||
if (dateBits[key] < 10) dateBits[key] = `0${dateBits[key]}`;
|
||||
});
|
||||
switch (mode) {
|
||||
case "DateDMY":
|
||||
return dateBits.day + symbol + dateBits.month + symbol + dateBits.year;
|
||||
case "DateDM":
|
||||
return dateBits.day + symbol + dateBits.month;
|
||||
case "DateYMD":
|
||||
return dateBits.year + symbol + dateBits.month + symbol + dateBits.day;
|
||||
case "DateDMYHM":
|
||||
return `${
|
||||
dateBits.day + symbol + dateBits.month + symbol + dateBits.year
|
||||
} ${dateBits.hours}:${dateBits.minutes}`;
|
||||
case "DateDMYHMS":
|
||||
return `${
|
||||
dateBits.day + symbol + dateBits.month + symbol + dateBits.year
|
||||
} ${dateBits.hours}:${dateBits.minutes}:${dateBits.seconds}`;
|
||||
case "DateYMDHM":
|
||||
return `${
|
||||
dateBits.year + symbol + dateBits.month + symbol + dateBits.day
|
||||
} ${dateBits.hours}:${dateBits.minutes}`;
|
||||
case "DateYMDHMS":
|
||||
return `${
|
||||
dateBits.year + symbol + dateBits.month + symbol + dateBits.day
|
||||
} ${dateBits.hours}:${dateBits.minutes}:${dateBits.seconds}`;
|
||||
case "DateHM":
|
||||
return `${dateBits.hours}:${dateBits.minutes}`;
|
||||
case "DateHMDM":
|
||||
return `${dateBits.hours}:${dateBits.minutes} ${dateBits.day}${symbol}${dateBits.month}`;
|
||||
case "DateDataBase":
|
||||
return `${
|
||||
dateBits.year + symbol + dateBits.month + symbol + dateBits.day
|
||||
}T${dateBits.hours}:${dateBits.minutes}:${dateBits.seconds}`;
|
||||
case "DateDataBaseSpace":
|
||||
return `${
|
||||
dateBits.year + symbol + dateBits.month + symbol + dateBits.day
|
||||
} ${dateBits.hours}:${dateBits.minutes}:${dateBits.seconds}`;
|
||||
default:
|
||||
return dateBits.day + symbol + dateBits.month + symbol + dateBits.year;
|
||||
}
|
||||
};
|
||||
|
||||
export const toStartDay = (date = new Date()) => {
|
||||
const selfDate = getDateByType(date);
|
||||
|
||||
return selfDate.setHours(0, 0, 0);
|
||||
};
|
||||
|
||||
export const withTime = (date, time) =>
|
||||
pipe(
|
||||
(x) => parseSepareteDate(x, "-", "DateYMD"),
|
||||
(x) => `${x}T${time}`
|
||||
)(date);
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<ButtonModal
|
||||
text="Выбрать машину"
|
||||
btnClass="col-span-4"
|
||||
containerClass="pt-10 px-4 min-w-[80vw]"
|
||||
>
|
||||
<Tabulator v-bind="tabulatorOtps" />
|
||||
</ButtonModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ButtonModal from '@molecules/ButtonModal/index.vue'
|
||||
import Tabulator from "@molecules/Tabulator/index.vue"
|
||||
|
||||
export default {
|
||||
name: 'Name',
|
||||
components: {ButtonModal, Tabulator},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
tabulatorOtps() {
|
||||
return {
|
||||
dataSource: this.packsData,
|
||||
columns: [
|
||||
{
|
||||
title: "Нормер пакета",
|
||||
field: "pack_number",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Количество записей",
|
||||
field: "count",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Последняя дата пакета",
|
||||
field: "pack_dt",
|
||||
width: 150,
|
||||
render: "{{ dt_format (dt_shift pack_dt 3 'hour') }}"
|
||||
}
|
||||
],
|
||||
height: "550px"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
223
live_monitor_vue/src/components/3_organisms/PackColumns.js
Normal file
223
live_monitor_vue/src/components/3_organisms/PackColumns.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import { parse } from "handlebars/dist/cjs/handlebars/compiler/base";
|
||||
import { cond, T, isEmpty } from "ramda";
|
||||
|
||||
const setPackColumns = (name) => {
|
||||
switch(name) {
|
||||
case 'КолонкиПакетов':
|
||||
return [
|
||||
{
|
||||
title: "Пакет на проде",
|
||||
field: "switch ",
|
||||
width: 150,
|
||||
render: `
|
||||
<form x-data='{ isProd: {{ isProd }} }' phx-change='update_pack_struct_online:{{ id }}'>
|
||||
<div class='flex justify-center'>
|
||||
<div class='form-check form-switch'>
|
||||
<label for='toogle_group_{{ id }}' class='flex items-center cursor-pointer'>
|
||||
<input value='true' x-model='isProd' id='toogle_group_{{ id }}' name='toogle_pack_struct_{{ id }}' class='form-check-input appearance-none w-9 rounded-full float-left h-3 align-top bg-white bg-no-repeat bg-contain bg-gray-300 checked:bg-blue-500 focus:outline-none cursor-pointer shadow-sm' type='checkbox' role='switch' id='flexSwitchCheckDefault'>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`
|
||||
},
|
||||
{
|
||||
title: "Название",
|
||||
field: "name",
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: "Действия",
|
||||
field: "actions",
|
||||
width: 100,
|
||||
render: `
|
||||
<div class='flex gap-2 py-1'>
|
||||
<button style='width: 25px; height: 25px;' class='italic border border-solid transition-all duration-300 rounded-full cursor-pointer hover:text-gray-500 linear-loader card-btn-action pt-[2px] text-primary' phx-click='select_struct:{{ id }}' >
|
||||
<i class='ri-eye-fill'></i>
|
||||
</button>
|
||||
<button style='width: 25px; height: 25px;' class='italic border border-solid transition-all duration-300 rounded-full cursor-pointer hover:text-gray-500 linear-loader card-btn-action'>
|
||||
<i class='ri-pencil-fill'></i>
|
||||
</button>
|
||||
<button style='width: 25px; height: 25px;' class='relative flex items-center justify-center italic border border-solid transition-all duration-300 rounded-full cursor-pointer hover:text-gray-500 linear-loader card-btn-action-red' x-data='{ open: false }' @click='open = !open'>
|
||||
<div x-show='open' class='popup-text not-italic'>
|
||||
<span>Вы точно хотите удалить структуру?</span>
|
||||
<span class='flex gap-2'>
|
||||
<span class='justify-center flex flex-grow bg-blue-500 hover:bg-blue-700 text-white rounded'>Да</span>
|
||||
<span class='justify-center flex flex-grow bg-gray-500 hover:bg-gray-700 text-white rounded'>Отмена</span>
|
||||
</span>
|
||||
</div>
|
||||
<i class='ri-delete-bin-fill'></i>
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
]
|
||||
case 'Сырые':
|
||||
return [
|
||||
{
|
||||
title: "Время пакета",
|
||||
field: "pack_dt",
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: "Архивная дата",
|
||||
field: "arhiv_date",
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: "Номер пакета",
|
||||
field: "pack_number",
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
title: "Длинна пакета",
|
||||
field: "pack_len",
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
title: "Репл",
|
||||
field: "id_repl",
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
title: "IMEI",
|
||||
field: "imei",
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: "ДАННЫЕ",
|
||||
field: "data",
|
||||
width: 600
|
||||
},
|
||||
{
|
||||
title: "Дата начала передачи",
|
||||
field: "connect_dt",
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: "Дата завершения передачи",
|
||||
field: "disconnect_dt",
|
||||
width: 180
|
||||
}
|
||||
]
|
||||
case 'Нераспознанные':
|
||||
return [
|
||||
{
|
||||
title: "Время пакета",
|
||||
field: "pack_dt",
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: "Архивная дата",
|
||||
field: "arhiv_date",
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: "Номер пакета",
|
||||
field: "pack_number",
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
title: "Длинна пакета",
|
||||
field: "pack_len",
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
title: "Репл",
|
||||
field: "id_repl",
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
title: "IMEI",
|
||||
field: "imei",
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: "ДАННЫЕ",
|
||||
field: "data",
|
||||
width: 600
|
||||
},
|
||||
{
|
||||
title: "Дата начала передачи",
|
||||
field: "connect_dt",
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: "Дата завершения передачи",
|
||||
field: "disconnect_dt",
|
||||
width: 180
|
||||
}
|
||||
]
|
||||
default: return []
|
||||
}
|
||||
}
|
||||
|
||||
const prepareSetPackColumns = (name, data) => {
|
||||
console.log('prepareSetPackColumns data', data);
|
||||
console.log('prepareSetPackColumns name', name);
|
||||
switch(name) {
|
||||
case 'askr_logs':
|
||||
if (data.length > 0) {
|
||||
return data[0].map((el) => ({title: Object.keys(el)[0], field: Object.keys(el)[0], width: 150}))
|
||||
}
|
||||
if (data.length === 0) {
|
||||
return []
|
||||
}
|
||||
case 'raw':
|
||||
return setPackColumns('Сырые')
|
||||
case 'unrecognized':
|
||||
return setPackColumns('Нераспознанные')
|
||||
default: return []
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeCols = (cols) => {
|
||||
console.log('normalizeCols - cols', cols)
|
||||
const withoutCoordsPackDtCols = cols.filter((el) => el.field !== 'pack_dt' && el.field !== 'lat_lon')
|
||||
const coordsField = {
|
||||
title: "Координаты",
|
||||
field: "lat_lon",
|
||||
key: "coords",
|
||||
render: `(_, rec) => {
|
||||
if (!rec?.geo) return ""
|
||||
const lat = rec?.geo?.coordinates[1]
|
||||
const lon = rec?.geo?.coordinates[0]
|
||||
return \`<div class="underline underline-offset-2 decoration-dashed cursor-pointer" phx-click="load_map" phx-value-lon="\${lat}" phx-value-lat="\${lon}">
|
||||
\${lat ? +lat.toFixed(5) : lat} \${lon ? +lon.toFixed(5) : lon}
|
||||
</div>\`
|
||||
}`
|
||||
}
|
||||
const packDtField = {
|
||||
title: "Дата формирования пакета",
|
||||
field: "pack_dt",
|
||||
key: "pack_dt",
|
||||
render: `{{ dt_format (dt_shift pack_dt 3 'hour') }}`,
|
||||
width: 130
|
||||
}
|
||||
return [packDtField, coordsField, ...withoutCoordsPackDtCols]
|
||||
}
|
||||
|
||||
const parseCols = (packStructure, report) => {
|
||||
if (!isEmpty(packStructure)) return normalizeCols(packStructure)
|
||||
if (!packStructure && report && !isEmpty(report)) return prepareSetPackColumns('askr_logs', report.askr_logs)
|
||||
if (!packStructure && !report) return []
|
||||
}
|
||||
|
||||
const fetchColumn = (packStructure, report, id = null) => {
|
||||
console.log('packStructure', packStructure)
|
||||
console.log('id', id)
|
||||
const getCurrentPackColls = (packStructure, id) => {
|
||||
const currentPackStructure = packStructure.find((el) => el.id === id)
|
||||
console.log('currentPackStructure', currentPackStructure)
|
||||
const currColumns = currentPackStructure.columns ? currentPackStructure.columns : []
|
||||
return parseCols(currColumns, null)
|
||||
}
|
||||
return cond([
|
||||
[(packStructure) => !packStructure === true, () => parseCols(null, report)],
|
||||
[(packStructure) => (packStructure?.length >= 1) && (id && typeof id === 'number'), () => getCurrentPackColls(packStructure, id)],
|
||||
[(packStructure) => packStructure?.length >= 1, () => parseCols(packStructure[0].columns, null)],
|
||||
[T, () => parseCols(null, report)],
|
||||
])(packStructure)
|
||||
}
|
||||
|
||||
export {prepareSetPackColumns, setPackColumns, normalizeCols, parseCols, fetchColumn}
|
||||
@@ -0,0 +1,58 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex"
|
||||
|
||||
export default {
|
||||
name: 'PackViewButton',
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
inner: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
labelClass: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
idx: {
|
||||
type: Number,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('machines', ['selectedPackMode']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions('machines', ['updateModePack']),
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
{{ console.log('selectedPackMode', selectedPackMode)
|
||||
}}
|
||||
<button @click="updateModePack(mode)" v-bind:key="idx" type="button" class="w-full">
|
||||
<input name="switchTableMode" type="radio" :id="name" :value="name" :checked="active" class="hidden peer" required="">
|
||||
<label :for="name" :class="labelClass" class="transition-all w-full border-slate-600 w-full block cursor-pointer py-1 text-ssm sm:text-sm hover:bg-slate-700 hover:text-white text-slate-600 border-slate-600
|
||||
peer-checked:bg-slate-600 peer-checked:text-white">
|
||||
{{ inner }}
|
||||
</label>
|
||||
</button>
|
||||
</template>
|
||||
123
live_monitor_vue/src/components/3_organisms/PackView/index.vue
Normal file
123
live_monitor_vue/src/components/3_organisms/PackView/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex"
|
||||
import PackViewButton from './Button.vue'
|
||||
import Spinner from "@molecules/Spinner/index.vue"
|
||||
import Tabulator from "@molecules/Tabulator/index.vue"
|
||||
import { isEmpty } from "ramda";
|
||||
|
||||
export default {
|
||||
name: 'PackView',
|
||||
components: {
|
||||
PackViewButton,
|
||||
Spinner,
|
||||
Tabulator
|
||||
},
|
||||
props: {
|
||||
isLoader: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
hasData: false
|
||||
}
|
||||
},
|
||||
setup (props, {slots}) {
|
||||
const hasDescription = slots.description != undefined
|
||||
const hasError = slots.error != undefined
|
||||
console.log('slots', slots)
|
||||
return {
|
||||
hasDescription,
|
||||
hasError
|
||||
}
|
||||
},
|
||||
updated () {
|
||||
this.$nextTick(function () {
|
||||
if (!isEmpty(this.packData)) {
|
||||
this.hasData = true
|
||||
}
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('machines', ['packData', 'selectPackStructs', 'selectedPackStruct']),
|
||||
buttonsData: () =>
|
||||
([
|
||||
{mode: 'askr_logs', isActive: true, name: 'switch_askr_logs', inner: 'обработанные', labelClass: 'rounded-l-lg border'},
|
||||
{mode: 'raw', isActive: false, name: 'switch_raw', inner: 'сырые', labelClass: 'border-y'},
|
||||
{mode: 'unrecognized', isActive: false, name: 'switch_unrecognized', inner: 'нераспознанные', labelClass: 'rounded-r-lg border'},
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
...mapActions('machines', ['updateSelectStruct']),
|
||||
updateSelectedValue: function (e) {
|
||||
console.log('e', e.target.value)
|
||||
this.updateSelectStruct(Number(e.target.value))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div class="w-full relative">
|
||||
{{
|
||||
console.log('packData', packData)
|
||||
}}
|
||||
<div v-if="!hasData" class="w-full h-[50px] rounded-xl bg-slate-200 hidden" :id="`${id}_loader`">
|
||||
<Spinner/>
|
||||
</div>
|
||||
<div v-if="hasData" :id="id" class="w-full rounded-md bg-light border-rounded p-4">
|
||||
<div class="grid grid-cols-12 mb-6">
|
||||
<div class="flex flex-col gap-1 col-span-12 sm:col-span-4 md:col-span-5 lg:col-span-5 2xl:col-span-7 sm:mb-0">
|
||||
<h2 :id="`${id}_pack_name`" class=" text-lg">Пакет: <span v-if="selectPackStructs" >{{selectPackStructs[0]?.packNumber}}</span></h2>
|
||||
<div v-if="hasDescription" class="text-xs text-slate-500">
|
||||
<slot name="description" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- <form phx-change="c_packs_mode:on_click" :id="`${id}_table_active`" class="col-span-12 sm:col-span-8 md:col-span-7 lg:col-span-7 2xl:col-span-5 flex justify-center hidden"> -->
|
||||
<div class="col-span-12 sm:col-span-8 md:col-span-7 lg:col-span-7 2xl:col-span-5 flex justify-center">
|
||||
<PackViewButton v-for="(button, idx) in buttonsData"
|
||||
:idx="idx"
|
||||
:mode="button.mode"
|
||||
:name="button.name"
|
||||
:active="button.isActive"
|
||||
:inner="button.inner"
|
||||
:labelClass="button.labelClass"
|
||||
/>
|
||||
</div>
|
||||
<!-- </form> -->
|
||||
</div>
|
||||
<div class="relative" :id="id">
|
||||
<div id="selectStructContainer" class="flex gap-2 items-center mb-2">
|
||||
<span>Выберите структуру:</span>
|
||||
<select v-if="selectPackStructs" id="selectStruct" name="struct" v-bind:value="selectedPackStruct.id" v-on:change="updateSelectedValue" class=" w-fit bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" style="--tw-ring-color:focus: none" >
|
||||
<option :key={idx} v-for="(el, idx) in selectPackStructs" :value="el.id">
|
||||
{{ el.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div :id="`${id}_report`" class="tab-pane active bg-light relative shadow text-center min-h-[300px] relative rounded-md w-full relative">
|
||||
<Tabulator v-bind="packData" />
|
||||
<div v-if="isLoader" class="w-full h-[50px] rounded-xl bg-slate-200 hidden" :id="`${id}_loader`">
|
||||
<Spinner/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hasError" :id="`${id}_error`" class="w-full rounded-md bg-light border-rounded p-4 flex flex-col gap-5 items-center justify-center hidden">
|
||||
<slot name="error" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
10
live_monitor_vue/src/components/4_frames/Head.vue
Normal file
10
live_monitor_vue/src/components/4_frames/Head.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full pl-2 flex items-center h-[40px] border-b border-slate-200">
|
||||
<div>
|
||||
Hi!
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'BreadCrumbs',
|
||||
components: {},
|
||||
props: {
|
||||
breadcrumbs: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
rkey() {
|
||||
return Math.random().toString(36).slice(4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- BEGIN: Breadcrumb -->
|
||||
<nav
|
||||
v-if="breadcrumbs"
|
||||
aria-label="breadcrumb"
|
||||
class="-intro-x h-[45px] mr-auto"
|
||||
>
|
||||
<ol class="breadcrumb breadcrumb-light">
|
||||
<li
|
||||
v-for="{name, link, active} in breadcrumbs"
|
||||
:key="rkey(name)"
|
||||
:class="{'breadcrumb-item active': active, 'breadcrumb-item': !active}"
|
||||
aria-current="page"
|
||||
>
|
||||
<a
|
||||
:href="link"
|
||||
>{{ name }}</a>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<!-- END: Breadcrumb -->
|
||||
<div class="flex grow w-full border-hide-600" />
|
||||
</template>
|
||||
43
live_monitor_vue/src/components/4_frames/Menu/LeftPanel.vue
Normal file
43
live_monitor_vue/src/components/4_frames/Menu/LeftPanel.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'LeftPanel',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
mobile_menu_opened: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center border-white/[0.2] border-r">
|
||||
<div
|
||||
class="flex p-4 cursor-pointer flex md:hidden"
|
||||
@click="mobile_menu_opened = !mobile_menu_opened"
|
||||
>
|
||||
<i
|
||||
icon="bar-chart-2"
|
||||
class="w-6 h-6 text-white transform rotate-90"
|
||||
/>
|
||||
</div>
|
||||
<router-link
|
||||
class="items-center relative -intro-x flex cursor-pointer p-4 hidden md:flex"
|
||||
to="/?page=machines&mode=cards"
|
||||
>
|
||||
<div
|
||||
class="text-white mx-4"
|
||||
style="font-weight: 600;"
|
||||
>
|
||||
LiveMonitor
|
||||
</div>
|
||||
</router-link>
|
||||
<div class="w-[15px]" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,75 @@
|
||||
<script>
|
||||
import MenuList from './MenuList.vue'
|
||||
import MobileMenu from './MobileMenu.vue'
|
||||
import LeftPanel from './LeftPanel.vue'
|
||||
import Breadcrumbs from './Breadcrumbs.vue'
|
||||
import RightPanel from './RightPanel.vue'
|
||||
import {mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'MenuContainer',
|
||||
components: {
|
||||
MobileMenu,
|
||||
MenuList,
|
||||
LeftPanel,
|
||||
Breadcrumbs,
|
||||
RightPanel
|
||||
},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('layout', ["show_menu", 'is_enabled_menu']),
|
||||
...mapGetters('auth', ['menuList']),
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main w-full">
|
||||
<!-- BEGIN: TOP BAR -->
|
||||
<div
|
||||
id="menu"
|
||||
class="top-bar-boxed w-full h-[59px] mt-[4px] z-[51] relative justify-center border-b border-white/[0.2] -mx-3 px-3 sm:pl-8 md:pt-0 mb-[4px]"
|
||||
>
|
||||
<MobileMenu
|
||||
v-if="false"
|
||||
current_menu_item=""
|
||||
current_user=""
|
||||
:menu_list="menuList"
|
||||
/>
|
||||
<div class="w-full">
|
||||
<div class="flex justify-between items-center">
|
||||
<LeftPanel />
|
||||
<Breadcrumbs
|
||||
v-if="false"
|
||||
:breadcrumbs="[]"
|
||||
/>
|
||||
<RightPanel
|
||||
:current_user="''"
|
||||
:page_title="''"
|
||||
:notifications="[{from: 'Бот из LM', datetime: '10:00', message: 'Проведен редизайн приложения', readed: false}]"
|
||||
/>
|
||||
</div>
|
||||
<div class="sm:hidden flex items-center justify-between" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: TOP BAR -->
|
||||
<div class="wrapper">
|
||||
<div class="wrapper-box">
|
||||
<div v-if="show_menu && is_enabled_menu">
|
||||
<MenuList
|
||||
:menu_list="menuList"
|
||||
/>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="grid grid-cols-12 gap-6 h-full">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
119
live_monitor_vue/src/components/4_frames/Menu/MenuItem.vue
Normal file
119
live_monitor_vue/src/components/4_frames/Menu/MenuItem.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<!-- eslint-disable vue/prop-name-casing -->
|
||||
<script>
|
||||
import { cond, equals, T, always } from 'ramda'
|
||||
import { createIcons, icons } from "lucide";
|
||||
import MenuItemGroup from './MenuItemGroup.vue'
|
||||
|
||||
export default {
|
||||
name: 'MenuItem',
|
||||
components: {MenuItemGroup},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
menu_item: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
link: "/"
|
||||
})
|
||||
},
|
||||
base_class: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
routeName: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
_base_class() {
|
||||
return `${this.base_class}`
|
||||
},
|
||||
activeClass () {
|
||||
return 'side-menu side-menu--active'
|
||||
},
|
||||
menuRouterName() {
|
||||
return this.menu_item.router || ""
|
||||
},
|
||||
currentRouterName() {
|
||||
return this.$router.currentRoute._value.name || ""
|
||||
},
|
||||
linkClass() {
|
||||
const menuRouterName = this.menu_item.router || ""
|
||||
if (menuRouterName === this.routeName)
|
||||
return `${this._base_class} ${this._base_class}--active`
|
||||
return this._base_class
|
||||
},
|
||||
_type() {
|
||||
return cond([
|
||||
[equals("simple_link"), (x) => x],
|
||||
[equals("line"), (x) => x],
|
||||
[equals("group"), (x) => x],
|
||||
[T, () => "other"],
|
||||
])(this.type)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.icons()
|
||||
},
|
||||
updated() {
|
||||
console.log(123)
|
||||
|
||||
this.icons()
|
||||
},
|
||||
methods: {
|
||||
icons() {
|
||||
createIcons({ icons, "stroke-width": 1.5, nameAttr: "icon" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<li v-if="_type == 'simple_link'">
|
||||
<router-link
|
||||
phx-hook="MenuLink"
|
||||
:class="linkClass"
|
||||
:to="menu_item.link"
|
||||
>
|
||||
<div :class="`${_base_class}__icon`">
|
||||
<i :icon="menu_item?.icon" />
|
||||
</div>
|
||||
<div :class="`${_base_class}__title`">
|
||||
{{ menu_item.show }}
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="_type == 'line'">
|
||||
<hr class="m-4">
|
||||
</li>
|
||||
<MenuItemGroup
|
||||
v-if="_type == 'group'"
|
||||
:type="_type"
|
||||
:base_class="base_class"
|
||||
:menu_item="menu_item"
|
||||
/>
|
||||
<div v-if="_type == 'other'">
|
||||
""
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- <router-link
|
||||
v-if="type === 'link'"
|
||||
class="flex p-2 rounded-md hover:bg-gray-100 flex items-center"
|
||||
:class="{'bg-gray-200': pageName === name}"
|
||||
:to="link"
|
||||
>
|
||||
<i :class="`ri-${icon} text-slate-600 mr-2 `" />
|
||||
<span
|
||||
v-if="isOpenMenu"
|
||||
class="text-slate-700"
|
||||
>{{ title }}</span>
|
||||
</router-link> -->
|
||||
@@ -0,0 +1,93 @@
|
||||
<!-- eslint-disable vue/prop-name-casing -->
|
||||
<script>
|
||||
import { cond, equals, T } from 'ramda'
|
||||
import { createIcons, icons } from "lucide";
|
||||
|
||||
export default {
|
||||
name: 'MenuItemCopy',
|
||||
components: {},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
menu_item: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
link: "/"
|
||||
})
|
||||
},
|
||||
base_class: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
_base_class() {
|
||||
return `${this.base_class}`
|
||||
},
|
||||
activeClass () {
|
||||
return 'side-menu side-menu--active'
|
||||
},
|
||||
menuRouterName() {
|
||||
return this.menu_item.router || ""
|
||||
},
|
||||
currentRouterName() {
|
||||
return this.$router.currentRoute._value.name || ""
|
||||
},
|
||||
linkClass() {
|
||||
const menuRouterName = this.menu_item.router || ""
|
||||
const currentRouterName = this.$router.currentRoute._value.name || Math.random()
|
||||
if (menuRouterName === currentRouterName)
|
||||
return `${this._base_class} ${this._base_class}--active`
|
||||
return this._base_class
|
||||
},
|
||||
_type() {
|
||||
return cond([
|
||||
[equals("simple_link"), (x) => x],
|
||||
[equals("line"), (x) => x],
|
||||
[equals("group"), (x) => x],
|
||||
[T, () => "other"],
|
||||
])(this.type)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.icons()
|
||||
},
|
||||
updated() {
|
||||
this.icons()
|
||||
},
|
||||
methods: {
|
||||
icons() {
|
||||
createIcons({ icons, "stroke-width": 1.5, nameAttr: "icon" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<li v-if="_type == 'simple_link'">
|
||||
<router-link
|
||||
phx-hook="MenuLink"
|
||||
:class="linkClass"
|
||||
:to="menu_item.link"
|
||||
>
|
||||
<div :class="`${_base_class}__icon`">
|
||||
<i :icon="menu_item?.icon" />
|
||||
</div>
|
||||
<div :class="`${_base_class}__title`">
|
||||
{{ menu_item.show }}
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="_type == 'line'">
|
||||
<hr class="m-4">
|
||||
</li>
|
||||
<div v-if="_type == 'other'">
|
||||
""
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,80 @@
|
||||
<!-- eslint-disable vue/prop-name-casing -->
|
||||
<script>
|
||||
import MenuItem from './MenuItemCopy.vue'
|
||||
|
||||
export default {
|
||||
name: 'MenuItemGroup',
|
||||
components: {
|
||||
MenuItem
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
base_class: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
menu_item: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
open: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
containerClass() {
|
||||
const menuRouterName = this.menu_item.router || ""
|
||||
const currentRouterName = this.$router.currentRoute._value.name || Math.random()
|
||||
if (menuRouterName === currentRouterName) return `${this.base_class} ${this.base_class}--active`
|
||||
return `${this.base_class}`
|
||||
},
|
||||
itemClass() {
|
||||
if (this.open) return `${this.base_class}__sub-icon transform rotate-180`
|
||||
return `${this.base_class}__sub-icon`
|
||||
},
|
||||
listClass() {
|
||||
if (this.open) return `${this.base_class}__sub-open`
|
||||
return ""
|
||||
},
|
||||
menu_list_with_index() {
|
||||
return this.menu_item.children.map((el, index) => ({...el, index}))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
this.open = !this.open
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<li>
|
||||
<span
|
||||
:class="containerClass"
|
||||
@click="toggle"
|
||||
>
|
||||
<div :class="`${base_class}__icon`"> <i :icon="menu_item.icon" /> </div>
|
||||
<div :class="`${base_class}__title`">
|
||||
{{ menu_item.show }}
|
||||
<div :class="itemClass"> <i icon="chevron-down" /> </div>
|
||||
</div>
|
||||
</span>
|
||||
<ul :class="listClass">
|
||||
<MenuItem
|
||||
v-for="item in menu_item.children"
|
||||
:key="JSON.stringify(item)"
|
||||
:base_class="base_class"
|
||||
:type="item.type"
|
||||
:menu_item="item"
|
||||
/>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
63
live_monitor_vue/src/components/4_frames/Menu/MenuList.vue
Normal file
63
live_monitor_vue/src/components/4_frames/Menu/MenuList.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<!-- eslint-disable vue/prop-name-casing -->
|
||||
<script>
|
||||
import {mapGetters} from 'vuex'
|
||||
import MenuItem from './MenuItem.vue'
|
||||
|
||||
export default {
|
||||
name: 'MenuList',
|
||||
components: {MenuItem},
|
||||
props: {
|
||||
current_menu_item: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
menu_list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
routeName: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('', ['mobile_menu_opened', 'show_menu']),
|
||||
menu_list_with_index() {
|
||||
return this.menu_list.map((el, index) => ({...el, index}))
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
$route (to){
|
||||
this.routeName = to.name;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rkey() {
|
||||
return Math.random().toString(36).slice(4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<nav
|
||||
x-show="show_menu"
|
||||
class="side-nav"
|
||||
>
|
||||
<ul>
|
||||
<MenuItem
|
||||
v-for="{index, ...menu_item} in menu_list_with_index"
|
||||
:id="`main_menu_item_component_${index}`"
|
||||
:key="rkey(index)"
|
||||
base_class="side-menu"
|
||||
:type="menu_item.type"
|
||||
:menu_item="menu_item"
|
||||
:current_menu_item="current_menu_item"
|
||||
:routeName="routeName"
|
||||
/>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
63
live_monitor_vue/src/components/4_frames/Menu/MobileMenu.vue
Normal file
63
live_monitor_vue/src/components/4_frames/Menu/MobileMenu.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script>
|
||||
import MenuItem from './MenuItem.vue'
|
||||
|
||||
export default {
|
||||
name: 'MobileMenu',
|
||||
components: {
|
||||
MenuItem
|
||||
},
|
||||
props: {
|
||||
// eslint-disable-next-line vue/prop-name-casing
|
||||
current_menu_item: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
// eslint-disable-next-line vue/prop-name-casing
|
||||
menu_list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
menu_list_with_index() {
|
||||
return this.menu_list.map((el, index) => ({...el, index}))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rkey() {
|
||||
return Math.random().toString(36).slice(4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div x-bind:class="mobile_menu_opened ? 'mobile-menu--active mobile-menu md:hidden' : 'mobile-menu md:hidden'">
|
||||
<div class="scrollable">
|
||||
<span
|
||||
class="mobile-menu-toggler"
|
||||
@click="mobile_menu_opened = !mobile_menu_opened"
|
||||
>
|
||||
<i
|
||||
icon="x-circle"
|
||||
class="w-8 h-8 text-white transform -rotate-90"
|
||||
/>
|
||||
</span>
|
||||
<ul class="scrollable__content py-2">
|
||||
<MenuItem
|
||||
v-for="{index, ...menu_item} in menu_list_with_index"
|
||||
:id="`main_menu_item_component_${index}`"
|
||||
:key="rkey(index)"
|
||||
base_class="menu"
|
||||
:type="menu_item.type"
|
||||
:menu_item="menu_item"
|
||||
:current_menu_item="current_menu_item"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
174
live_monitor_vue/src/components/4_frames/Menu/RightPanel.vue
Normal file
174
live_monitor_vue/src/components/4_frames/Menu/RightPanel.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<!-- eslint-disable vue/prop-name-casing -->
|
||||
<script>
|
||||
import {mapGetters, mapMutations} from 'vuex'
|
||||
import { createIcons, icons } from "lucide";
|
||||
|
||||
export default {
|
||||
name: 'RightPanel',
|
||||
components: {},
|
||||
props: {
|
||||
// eslint-disable-next-line vue/prop-name-casing
|
||||
page_title: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('auth', ['current_user'])
|
||||
},
|
||||
mounted() {
|
||||
this.icons()
|
||||
},
|
||||
updated() {
|
||||
this.icons()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('layout', ["toggle_menu"]),
|
||||
icons() {
|
||||
createIcons({ icons, "stroke-width": 1.5, nameAttr: "icon" })
|
||||
},
|
||||
rkey() {
|
||||
return Math.random().toString(36).slice(4)
|
||||
},
|
||||
closeDropDown() {
|
||||
setTimeout(() => {
|
||||
document.querySelector("body").click()
|
||||
}, 1);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-end grow gapy-2 px-4 border-hide-600">
|
||||
<div class="">
|
||||
{{ page_title }}
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="relative">
|
||||
<div class="intro-x dropdown">
|
||||
<div
|
||||
class="dropdown-toggle notification notification--bullet cursor-pointer"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
data-tw-toggle="dropdown"
|
||||
>
|
||||
<i
|
||||
icon="bell"
|
||||
class="cursor-pointer border-white/[0.2] border-r border-l p-4 text-white"
|
||||
style="width: 55px; height: 55px;"
|
||||
/>
|
||||
<div
|
||||
style="font-size: 7px; font-weight: 900; top: 10px; left: 30px"
|
||||
class="h-[13px] w-[13px] bg-white rounded-full mr-8 transition absolute flex itens-center justify-center"
|
||||
>
|
||||
<span
|
||||
style="line-height: 13px; font-weight: 900; font-size: 7px"
|
||||
class="text-black"
|
||||
>0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification-content pt-2 dropdown-menu">
|
||||
<div class="notification-content__box dropdown-content">
|
||||
<div class="notification-content__title">
|
||||
Оповещения
|
||||
</div>
|
||||
<div
|
||||
v-for="{from, datetime, message, readed} in notifications"
|
||||
:key="rkey(message)"
|
||||
class="cursor-pointer relative flex items-center "
|
||||
>
|
||||
<div :class="{'bg-gray-400 w-3 h-3 absolute right-0 bottom-0 rounded-full border-2 border-white dark:border-darkmode-600|, else: ~s|bg-success w-3 h-3 absolute right-0 bottom-0 rounded-full border-2 border-white dark:border-darkmode-600': readed}" />
|
||||
<div class="ml-2 overflow-hidden">
|
||||
<div class="flex items-center">
|
||||
<a
|
||||
href="javascript:;"
|
||||
class="font-medium truncate mr-5"
|
||||
>{{ from }}</a>
|
||||
<div class="text-xs text-slate-400 ml-auto whitespace-nowrap">
|
||||
{{ datetime }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full truncate text-slate-500 mt-0.5">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cursor-pointer border-white/[0.2] border-r p-4">
|
||||
<i
|
||||
id="menu_book_icon"
|
||||
icon="book"
|
||||
class="text-white w-[24px] h-[24px]"
|
||||
/>
|
||||
</div>
|
||||
<div class="cursor-pointer border-white/[0.2] border-r p-4">
|
||||
<i
|
||||
id="menu_question_icon"
|
||||
icon="help-circle"
|
||||
class="text-white w-[24px] h-[24px]"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="cursor-pointer hidden md:flex border-white/[0.2] border-r p-4"
|
||||
@click="toggle_menu"
|
||||
>
|
||||
<i
|
||||
id="menu_library"
|
||||
icon="library"
|
||||
class="text-white w-[24px] h-[24px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN: Account Menu -->
|
||||
<div class="intro-x dropdown w-[50px] h-[50px] px-3 flex justify-center items-center">
|
||||
<div
|
||||
class="dropdown-toggle rounded-full overflow-hidden shadow-lg image-fit zoom-in scale-110"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
data-tw-toggle="dropdown"
|
||||
>
|
||||
<img
|
||||
alt="image"
|
||||
src="/images/profile-4.jpg"
|
||||
>
|
||||
</div>
|
||||
<div class="dropdown-menu w-56">
|
||||
<ul class="dropdown-content before:block before:absolute before:bg-black before:inset-0 before:rounded-md before:z-[-1]">
|
||||
<li class="p-2">
|
||||
<div class="font-bold">
|
||||
{{ current_user?.first_name || "" }} {{ current_user?.last_name || "" }}
|
||||
</div>
|
||||
<div
|
||||
class="text-xs text-black/60 dark:text-slate-500"
|
||||
style="margin-top: -0.25rem;"
|
||||
>
|
||||
{{ current_user?.position || "позиция не указана" }}
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider border-white/[0.08]">
|
||||
</li>
|
||||
<li @click="closeDropDown">
|
||||
<router-link
|
||||
to="/users/log_out"
|
||||
class="dropdown-item hover:bg-white/5 hover:text-black"
|
||||
>
|
||||
Выйти
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Account Menu -->
|
||||
</div>
|
||||
</template>
|
||||
20
live_monitor_vue/src/components/4_frames/Menu/index.vue
Normal file
20
live_monitor_vue/src/components/4_frames/Menu/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
139
live_monitor_vue/src/components/4_frames/SideMenu.vue
Normal file
139
live_monitor_vue/src/components/4_frames/SideMenu.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div
|
||||
class="h-[100vh] z-40 bg-white flex flex-col px-4 pt-8 border-r border-slate-200 transition-all"
|
||||
:class="{'w-[300px]': isOpenMenu, 'w-[70px]': !isOpenMenu}"
|
||||
>
|
||||
<div class="pl-1 flex items-center mb-8 justify-between">
|
||||
<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
|
||||
offset="100%"
|
||||
stop-color="#BD34FE"
|
||||
/></linearGradient><linearGradient
|
||||
id="IconifyId1813088fe1fbc01fb467"
|
||||
x1="43.376%"
|
||||
x2="50.316%"
|
||||
y1="2.242%"
|
||||
y2="89.03%"
|
||||
><stop
|
||||
offset="0%"
|
||||
stop-color="#FFEA83"
|
||||
/><stop
|
||||
offset="8.333%"
|
||||
stop-color="#FFDD35"
|
||||
/><stop
|
||||
offset="100%"
|
||||
stop-color="#FFA800"
|
||||
/></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
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="p-1 px-2 rounded-md transition-all cursor-pointer hover:bg-gray-200"
|
||||
:class="{'rotate-90': !isOpenMenu, '-rotate-90': isOpenMenu}"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<i class="ri-upload-line" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
v-for="{title, icon, link, type, name} in menu"
|
||||
:key="`${title}${name}`"
|
||||
>
|
||||
<router-link
|
||||
v-if="type === 'link'"
|
||||
class="flex p-2 rounded-md hover:bg-gray-100 flex items-center"
|
||||
:class="{'bg-gray-200': pageName === name}"
|
||||
:to="link"
|
||||
>
|
||||
<i :class="`ri-${icon} text-slate-600 mr-2 `" />
|
||||
<span
|
||||
v-if="isOpenMenu"
|
||||
class="text-slate-700"
|
||||
>{{ title }}</span>
|
||||
</router-link>
|
||||
<div
|
||||
v-if="type === 'line'"
|
||||
class="border-b my-4 border-slate-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapMutations} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
const menu = [
|
||||
{
|
||||
title: "Главная",
|
||||
icon: "home-4-line",
|
||||
link: "/",
|
||||
type: "link",
|
||||
name: "main"
|
||||
},
|
||||
{
|
||||
title: "Главная1",
|
||||
icon: "home-4-line",
|
||||
link: "/page1",
|
||||
type: "link",
|
||||
name: "page1"
|
||||
},
|
||||
{
|
||||
title: "Главная2",
|
||||
icon: "home-4-line",
|
||||
link: "/page2",
|
||||
type: "link",
|
||||
name: "page2"
|
||||
},
|
||||
{
|
||||
title: "Выход",
|
||||
icon: "logout-circle-line",
|
||||
link: "/auth",
|
||||
type: "link",
|
||||
name: ""
|
||||
},
|
||||
]
|
||||
return {
|
||||
menu
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('layout', ['isOpenMenu']),
|
||||
pageName() {
|
||||
return this.$route.name
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('layout', ['toggleMenu']),
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
982
live_monitor_vue/src/components/5_pages/404/Car.vue
Normal file
982
live_monitor_vue/src/components/5_pages/404/Car.vue
Normal file
File diff suppressed because one or more lines are too long
159
live_monitor_vue/src/components/5_pages/404/index.vue
Normal file
159
live_monitor_vue/src/components/5_pages/404/index.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="hidden 2xl:flex w-full h-[100vh] overflow-hidden bg-white">
|
||||
<div class="h-full overflow-hidden flex flex-col justify-between translate-y-1">
|
||||
<div class="text-3xl text-sky-800 pl-10 pt-10">
|
||||
<router-link to="/">
|
||||
Вернуться на главную
|
||||
</router-link>
|
||||
</div>
|
||||
<img
|
||||
width="100%"
|
||||
src="/images/not_found.jpg"
|
||||
alt="404"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="2xl:hidden flex w-full items-center justify-center h-[100vh] ">
|
||||
<div
|
||||
id="notfound"
|
||||
class="notfound h-fit"
|
||||
>
|
||||
<div class="notfound-404">
|
||||
<div />
|
||||
<h1>404</h1>
|
||||
</div>
|
||||
<h2 class="mb-4">
|
||||
Страница не найдена
|
||||
</h2>
|
||||
<router-link to="/">
|
||||
На главную
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
#notfound {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#notfound .notfound {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(-50%,-50%);
|
||||
-ms-transform: translate(-50%,-50%);
|
||||
transform: translate(-50%,-50%)
|
||||
}
|
||||
|
||||
.notfound {
|
||||
max-width: 460px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
line-height: 1.4
|
||||
}
|
||||
|
||||
.notfound .notfound-404 {
|
||||
position: relative;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
margin: 0 auto 50px
|
||||
}
|
||||
|
||||
.notfound .notfound-404>div:first-child {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: #ffa200;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
border: 5px dashed #000;
|
||||
border-radius: 5px
|
||||
}
|
||||
|
||||
.notfound .notfound-404>div:first-child:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -5px;
|
||||
right: -5px;
|
||||
bottom: -5px;
|
||||
top: -5px;
|
||||
-webkit-box-shadow: 0 0 0 5px rgba(0,0,0,.1) inset;
|
||||
box-shadow: 0 0 0 5px rgba(0,0,0,.1) inset;
|
||||
border-radius: 5px
|
||||
}
|
||||
|
||||
.notfound .notfound-404 h1 {
|
||||
font-family: cabin,sans-serif;
|
||||
color: #000;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
font-size: 90px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(-50%,-50%);
|
||||
-ms-transform: translate(-50%,-50%);
|
||||
transform: translate(-50%,-50%);
|
||||
left: 50%;
|
||||
text-align: center;
|
||||
height: 40px;
|
||||
line-height: 40px
|
||||
}
|
||||
|
||||
.notfound h2 {
|
||||
font-family: cabin,sans-serif;
|
||||
font-size: 33px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 7px
|
||||
}
|
||||
|
||||
.notfound p {
|
||||
font-family: cabin,sans-serif;
|
||||
font-size: 16px;
|
||||
color: #000;
|
||||
font-weight: 400
|
||||
}
|
||||
|
||||
.notfound a {
|
||||
font-family: cabin,sans-serif;
|
||||
display: inline-block;
|
||||
padding: 10px 25px;
|
||||
background-color: #8f8f8f;
|
||||
border: none;
|
||||
border-radius: 40px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
-webkit-transition: .2s all;
|
||||
transition: .2s all
|
||||
}
|
||||
|
||||
.notfound a:hover {
|
||||
background-color: #2c2c2c
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
194
live_monitor_vue/src/components/5_pages/Auth/Login.vue
Normal file
194
live_monitor_vue/src/components/5_pages/Auth/Login.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<script>
|
||||
import {mapGetters, mapActions, mapMutations} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'LogIn',
|
||||
components: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
login: "",
|
||||
password: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('auth', ['errorMsg', "my_comp"])
|
||||
},
|
||||
mounted() {
|
||||
localStorage.removeItem('token')
|
||||
},
|
||||
unmounted() {
|
||||
this.login = ""
|
||||
this.password = ""
|
||||
},
|
||||
methods: {
|
||||
...mapActions('auth', ['signin']),
|
||||
...mapMutations('auth', ["set_my_comp" ])
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<div
|
||||
class="login"
|
||||
style="max-height: 100vh"
|
||||
>
|
||||
<div class="container sm:px-10">
|
||||
<div class="block xl:grid grid-cols-2 gap-4">
|
||||
<!-- BEGIN: Login Info -->
|
||||
<div
|
||||
style="z-index: 1;"
|
||||
class="hidden relative xl:flex flex-col min-h-screen"
|
||||
>
|
||||
<div class="my-auto">
|
||||
<div class="-intro-x text-white font-medium text-4xl leading-tight">
|
||||
<img
|
||||
alt="логотип"
|
||||
class="mb-8"
|
||||
src="/images/illustration.svg"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
href="/"
|
||||
class="-intro-x flex items-center gap-2 pt-5 text-white hover:text-white"
|
||||
>
|
||||
<img
|
||||
alt="логотип"
|
||||
style="width: 40px; height: 40px"
|
||||
src="/images/favicon-white.ico"
|
||||
>
|
||||
Live Monitor
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="-intro-x mt-5 text-lg text-white text-opacity-70 dark:text-slate-400">
|
||||
Мониторинг СПС максимально приближенный
|
||||
<br>
|
||||
к режиму реального времени
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- END: Login Info -->
|
||||
<!-- BEGIN: Login Form -->
|
||||
<div class="h-screen xl:h-auto flex py-5 xl:py-0 my-10 xl:my-0">
|
||||
<div class="my-auto mx-auto xl:ml-20 bg-white dark:bg-darkmode-600 xl:bg-transparent px-5 sm:px-8 py-8 xl:p-0 rounded-md shadow-md xl:shadow-none w-full sm:w-3/4 lg:w-2/4 xl:w-auto">
|
||||
<h2 class="intro-x font-bold text-2xl xl:text-3xl dark:text-gray-100 text-center xl:text-left">
|
||||
Вход
|
||||
</h2>
|
||||
<div class="intro-x mt-2 text-slate-400 xl:hidden text-center">
|
||||
Мониторинг СПС максимально приближенный
|
||||
к режиму реального времени
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="login_form"
|
||||
phx-update="ignore"
|
||||
>
|
||||
<div class="space-y-4 bg-white mt-10 dark:bg-darkmode-600">
|
||||
<div phx-feedback-for="user[login]">
|
||||
<label
|
||||
for="user_login"
|
||||
class="block text-sm leading-6 text-zinc-800 dark:text-white"
|
||||
>
|
||||
Логин
|
||||
</label>
|
||||
<input
|
||||
id="user_login"
|
||||
v-model="login"
|
||||
type="text"
|
||||
name="login"
|
||||
class="block w-full rounded-lg border-zinc-300 py-[1px] px-[11px] text-zinc-900 focus:outline-none focus:ring-4 text-sm sm:leading-6 phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5 border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5 "
|
||||
required=""
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="relative"
|
||||
phx-feedback-for="user[password]"
|
||||
>
|
||||
<label
|
||||
for="user_password"
|
||||
class="block text-sm leading-6 text-zinc-800 dark:text-white"
|
||||
>
|
||||
Пароль
|
||||
</label>
|
||||
<input
|
||||
id="user_password"
|
||||
v-model="password"
|
||||
type="password"
|
||||
name="password"
|
||||
class="block w-full rounded-lg border-zinc-300 py-[1px] px-[11px] text-zinc-900 focus:outline-none focus:ring-4 text-sm sm:leading-6 phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5 border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5 "
|
||||
required=""
|
||||
>
|
||||
<i
|
||||
id="show_password"
|
||||
class="cursor-pointer transition-all text-slate-500 hover:text-slate-800 absolute ri-eye-line bottom-[4px] right-[7px]"
|
||||
/>
|
||||
<i
|
||||
id="hide_password"
|
||||
class="hidden cursor-pointer transition-all text-slate-500 hover:text-slate-800 absolute ri-eye-off-line bottom-[4px] right-[7px]"
|
||||
/>
|
||||
<div
|
||||
v-if="errorMsg"
|
||||
class="text-red-600 mt-1 ml-1"
|
||||
>
|
||||
{{ errorMsg }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="intro-x flex text-slate-600 dark:text-slate-500 text-xs sm:text-sm mt-4">
|
||||
<div class="flex items-center mr-auto">
|
||||
<div phx-feedback-for="user[remember_me]">
|
||||
<label class="flex items-center gap-4 text-sm leading-6 text-zinc-600 dark:text-white">
|
||||
<input
|
||||
type="hidden"
|
||||
name="user[remember_me]"
|
||||
value="false"
|
||||
>
|
||||
<input
|
||||
id="user_remember_me"
|
||||
:value="my_comp"
|
||||
type="checkbox"
|
||||
name="user[remember_me]"
|
||||
class="rounded border-zinc-300 text-zinc-900 focus:ring-zinc-900"
|
||||
@input="set_my_comp"
|
||||
>
|
||||
Мой компьютер
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<span />
|
||||
</div>
|
||||
<div class="intro-x mt-5 xl:mt-8 text-center xl:text-left flex">
|
||||
<button
|
||||
type="button"
|
||||
class="phx-submit-loading:opacity-75 rounded-lg bg-slate-700 hover:bg-slate-800 py-1 px-3 text-sm font-semibold leading-6 text-white active:text-white/80 mr-3 px-4"
|
||||
phx-disable-with="Вход..."
|
||||
style="width: 150px"
|
||||
@click="signin({login, password})"
|
||||
>
|
||||
Войти
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
style="width: 150px"
|
||||
onclick="toggleDark()"
|
||||
class="btn btn-outline-secondary py-1 w-full xl:w-36 mt-3 xl:mt-0 align-top"
|
||||
>
|
||||
Темный режим
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center my-6">
|
||||
<span class="inline-block text-sm text-blue-500 align-baseline hover:text-blue-800"> Восстановить пароль можно через обращение в НИАЦ АО ВНИИЖТ</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
22
live_monitor_vue/src/components/5_pages/Auth/index.vue
Normal file
22
live_monitor_vue/src/components/5_pages/Auth/index.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script>
|
||||
import Login from './Login.vue'
|
||||
|
||||
export default {
|
||||
name: 'Auth',
|
||||
components: {
|
||||
Login
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Login />
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
96
live_monitor_vue/src/components/5_pages/Cron/Task.vue
Normal file
96
live_monitor_vue/src/components/5_pages/Cron/Task.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="flex justify-between w-full border border-slate-300 p-1 flex-col md:flex-row hover:bg-slate-300 hover:border-slate-600 transition-all">
|
||||
<div class="flex gap-1 items-center">
|
||||
<div>
|
||||
<div class="flex justify-center">
|
||||
<div class="form-check form-switch">
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
value=""
|
||||
class="sr-only peer"
|
||||
checked
|
||||
>
|
||||
<div class="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-2 peer-focus:ring-green-700 dark:peer-focus:ring-green-800 dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-green-600" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>Название: askr_locations_speed_limit_calc</div>
|
||||
<div>Описание: Поиск случаев превышения автомобилями скорости передвижения по автодорогам</div>
|
||||
<div
|
||||
id="calc_period_59"
|
||||
current-period="3600000"
|
||||
phx-hook="TimeConvert"
|
||||
>
|
||||
Период: кажды(й/е) час
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div>
|
||||
<TaskHistory>
|
||||
<template #button>
|
||||
<i class="cursor-pointer ri-eye-line" />
|
||||
</template>
|
||||
</TaskHistory>
|
||||
</div>
|
||||
<div>
|
||||
<TaskModal>
|
||||
<template #button>
|
||||
<i class="cursor-pointer ri-pencil-line" />
|
||||
</template>
|
||||
</TaskModal>
|
||||
</div>
|
||||
<div x-data="{ open: false }">
|
||||
<i
|
||||
x-show="!open"
|
||||
class="cursor-pointer ri-delete-bin-line text-red-500"
|
||||
@click="open = true"
|
||||
/>
|
||||
<div
|
||||
class="flex my_margin-2 items-center"
|
||||
x-show="open"
|
||||
style="display: none;"
|
||||
@click="open = false"
|
||||
>
|
||||
<span
|
||||
phx-click="delete_period"
|
||||
phx-value-id="59"
|
||||
class="cursor-pointer p-0.5 border rounded text-red-600 border-red-600 mr-2"
|
||||
>Удалить</span>
|
||||
<span
|
||||
class="cursor-pointer rounded p-0.5 border border-slate-600 text-slate-600"
|
||||
@click="open = false"
|
||||
>Отмена</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TaskModal from './TaskModal.vue'
|
||||
import TaskHistory from './TaskHistory.vue'
|
||||
import ButtonModal from '@molecules/ButtonModal/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'Task',
|
||||
components: {
|
||||
TaskModal,
|
||||
TaskHistory
|
||||
},
|
||||
props: {
|
||||
index: {
|
||||
default: 0,
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
47
live_monitor_vue/src/components/5_pages/Cron/TaskHistory.vue
Normal file
47
live_monitor_vue/src/components/5_pages/Cron/TaskHistory.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<ButtonModal
|
||||
forcedBtnClass=" "
|
||||
containerClass="pt-10 px-4 w-[80vw] 2xl:w-[70vw]"
|
||||
:onToggle="onToggle"
|
||||
>
|
||||
<template #button>
|
||||
<span>
|
||||
<slot name="button" />
|
||||
</span>
|
||||
</template>
|
||||
1234
|
||||
</ButtonModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ButtonModal from '@molecules/ButtonModal/index.vue'
|
||||
import {data} from './historyData'
|
||||
|
||||
export default {
|
||||
name: 'TaskHistory',
|
||||
components: {
|
||||
ButtonModal,
|
||||
},
|
||||
props: {
|
||||
taskId: {
|
||||
default: "",
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
task: "",
|
||||
task_description: "",
|
||||
task_name: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
onToggle: ({isOpen}) => {
|
||||
console.log(isOpen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
256
live_monitor_vue/src/components/5_pages/Cron/TaskModal.vue
Normal file
256
live_monitor_vue/src/components/5_pages/Cron/TaskModal.vue
Normal file
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<ButtonModal
|
||||
forcedBtnClass=" "
|
||||
containerClass="pt-10 px-4 w-[80vw] 2xl:w-[70vw]"
|
||||
:onToggle="onToggle"
|
||||
>
|
||||
<template #button>
|
||||
<span>
|
||||
<slot name="button" />
|
||||
</span>
|
||||
</template>
|
||||
<form
|
||||
id="period_modal_form"
|
||||
phx-submit="form_validation"
|
||||
class="flex flex-col gap-3 p-4 w-full min-h-[70vh]"
|
||||
>
|
||||
<div class="relative hidden">
|
||||
<label
|
||||
for="id"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300 text-left "
|
||||
>
|
||||
Период (мс)
|
||||
</label>
|
||||
<input
|
||||
id="id"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
|
||||
placeholder="Период (мс)"
|
||||
name="id"
|
||||
type="text"
|
||||
value=""
|
||||
autocomplete="off"
|
||||
>
|
||||
</div>
|
||||
<h2 class="font-bold text-slate-600 text-xl">
|
||||
Описание
|
||||
</h2>
|
||||
<div class="flex justify-between items-center w-full gap-3">
|
||||
<div class="min-w-[150px]">
|
||||
<label
|
||||
for="dt_start"
|
||||
class="block font-w-700 text-sm text-gray-900 dark:text-gray-300"
|
||||
>Дата начала</label>
|
||||
<div
|
||||
class="block mb-2 text-sm text-gray-500 dark:text-gray-300"
|
||||
>
|
||||
календарь
|
||||
</div>
|
||||
<Datepicker
|
||||
:defaultDate="dtStart"
|
||||
:enableTime="true"
|
||||
:onchange="setDtStart"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-fit flex gap-2 items-end">
|
||||
<div class="px-4">
|
||||
<div class="font-semibold">
|
||||
Период
|
||||
</div>
|
||||
<div class="flex w-[800px] gap-2">
|
||||
<div class="relative ">
|
||||
<label
|
||||
for="month"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300 text-left "
|
||||
>
|
||||
месяц
|
||||
</label>
|
||||
<input
|
||||
id="period_month"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
|
||||
placeholder=""
|
||||
name="month"
|
||||
type="number"
|
||||
value=""
|
||||
autocomplete="off"
|
||||
>
|
||||
</div><div class="relative ">
|
||||
<label
|
||||
for="day"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300 text-left "
|
||||
>
|
||||
день
|
||||
</label>
|
||||
<input
|
||||
id="period_day"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
|
||||
placeholder=""
|
||||
name="day"
|
||||
type="number"
|
||||
value=""
|
||||
autocomplete="off"
|
||||
>
|
||||
</div><div class="relative ">
|
||||
<label
|
||||
for="hour"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300 text-left "
|
||||
>
|
||||
час
|
||||
</label>
|
||||
<input
|
||||
id="period_hour"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
|
||||
placeholder=""
|
||||
name="hour"
|
||||
type="number"
|
||||
value=""
|
||||
autocomplete="off"
|
||||
>
|
||||
</div><div class="relative ">
|
||||
<label
|
||||
for="minute"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300 text-left "
|
||||
>
|
||||
минута
|
||||
</label>
|
||||
<input
|
||||
id="period_minute"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
|
||||
placeholder=""
|
||||
name="minute"
|
||||
type="number"
|
||||
value=""
|
||||
autocomplete="off"
|
||||
>
|
||||
</div><div class="relative ">
|
||||
<label
|
||||
for="second"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300 text-left "
|
||||
>
|
||||
секунда
|
||||
</label>
|
||||
<input
|
||||
id="period_second"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
|
||||
placeholder=""
|
||||
name="second"
|
||||
type="number"
|
||||
value=""
|
||||
autocomplete="off"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-start items-start flex-col mb-2 w-full col-span-4 w-fit pl-4">
|
||||
<label
|
||||
for="dt_start"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 text-left dark:text-gray-300 font-semibold"
|
||||
>Задача включена</label>
|
||||
<div class="block mb-2 text-sm text-gray-500 dark:text-gray-300">
|
||||
переключить
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
value=""
|
||||
class="sr-only peer"
|
||||
:checked="false"
|
||||
>
|
||||
<div class="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-2 peer-focus:ring-green-700 dark:peer-focus:ring-green-800 dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full after:content-[''] after:absolute after:top-0.5 after:start-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 transition-all peer-checked:bg-green-600" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative ">
|
||||
<label
|
||||
for="name"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300 text-left "
|
||||
>
|
||||
Название задачи
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
|
||||
placeholder="Название задачи"
|
||||
name="name"
|
||||
type="text"
|
||||
value=""
|
||||
autocomplete="off"
|
||||
>
|
||||
</div>
|
||||
<fwb-textarea
|
||||
v-model="task_description"
|
||||
:rows="4"
|
||||
label="Описание задачи"
|
||||
placeholder="Описание задачи"
|
||||
class="w-full"
|
||||
/>
|
||||
<fwb-textarea
|
||||
v-model="task"
|
||||
:rows="14"
|
||||
label="Тело задачи"
|
||||
placeholder="тело задачи"
|
||||
class="w-full"
|
||||
/>
|
||||
<button
|
||||
v-if="taskId != 'new'"
|
||||
id="btn_update_period"
|
||||
type="submit"
|
||||
class="cursor-pointer text-white bg-primary hover:brightness-90 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-ssm w-full px-5 py-1 text-center transition-all w-[150px]"
|
||||
event="update_period"
|
||||
from-id="period_modal_form"
|
||||
phx-hook="SendFormData"
|
||||
>
|
||||
Обновить задачу
|
||||
</button>
|
||||
<button
|
||||
v-if="taskId == 'new'"
|
||||
id="btn_create_period"
|
||||
type="submit"
|
||||
class="cursor-pointer text-white bg-primary hover:brightness-90 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-ssm w-full px-5 py-1 text-center transition-all w-[150px]"
|
||||
event="create_period"
|
||||
from-id="period_modal_form"
|
||||
phx-hook="SendFormData"
|
||||
>
|
||||
Добавить задачу
|
||||
</button>
|
||||
</form>
|
||||
</ButtonModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ButtonModal from '@molecules/ButtonModal/index.vue'
|
||||
import Datepicker from "@molecules/Datepicker/index.vue"
|
||||
import { FwbTextarea } from 'flowbite-vue'
|
||||
|
||||
export default {
|
||||
name: 'TaskModal',
|
||||
components: {
|
||||
Datepicker,
|
||||
ButtonModal,
|
||||
FwbTextarea
|
||||
},
|
||||
props: {
|
||||
taskId: {
|
||||
default: "",
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
task: "",
|
||||
task_description: "",
|
||||
task_name: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
onToggle: ({isOpen}) => {
|
||||
console.log(isOpen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
2148
live_monitor_vue/src/components/5_pages/Cron/historyData.js
Normal file
2148
live_monitor_vue/src/components/5_pages/Cron/historyData.js
Normal file
File diff suppressed because it is too large
Load Diff
161
live_monitor_vue/src/components/5_pages/Cron/index.vue
Normal file
161
live_monitor_vue/src/components/5_pages/Cron/index.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3 bg-light h-full w-full col-span-12 rounded-xl p-2">
|
||||
<TaskModal taskId="new">
|
||||
<template #button>
|
||||
<button class="cursor-pointer bg-primary hover:bg-transparent text-white font-semibold hover:text-primary py-1 text-ssm px-4 border border-primary rounded-lg text-center transition_up w-[200px]">
|
||||
Добавить задачу
|
||||
</button>
|
||||
</template>
|
||||
</TaskModal>
|
||||
<div
|
||||
id="phx-F6ih6f3ULA2TYReS-4-0"
|
||||
data-phx-component="4"
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="hidden"
|
||||
phx-click="[["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#period_table_modal"}]]"
|
||||
/>
|
||||
<div
|
||||
id="period_table_modal"
|
||||
class="fixed z-[10000] inset-0 hidden"
|
||||
phx-remove="[["transition",{"time":200,"to":null,"transition":[["fade-out"],[],[]]}]]"
|
||||
>
|
||||
<!-- Modal container -->
|
||||
<div class="h-screen flex items-center justify-center p-4">
|
||||
<!-- Overlay -->
|
||||
<div
|
||||
class="absolute z-0 inset-0 bg-gray-500 opacity-75"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<!-- Modal box -->
|
||||
<div
|
||||
class="relative max-h-full overflow-y-auto bg-white rounded-lg shadow-xl "
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="0"
|
||||
autofocus=""
|
||||
phx-window-keydown="[["hide",{"time":200,"to":"#period_table_modal","transition":[["ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["dispatch",{"event":"click","to":"#period_table_modal-return"}]]"
|
||||
phx-click-away="[["hide",{"time":200,"to":"#period_table_modal","transition":[["ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["dispatch",{"event":"click","to":"#period_table_modal-return"}]]"
|
||||
phx-key="escape"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-2 right-2 text-gray-400 flex space-x-1 items-center"
|
||||
aria_label="close modal"
|
||||
phx-click="[["hide",{"time":200,"to":"#period_table_modal","transition":[["ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["dispatch",{"event":"click","to":"#period_table_modal-return"}]]"
|
||||
>
|
||||
<span class="text-sm">(esc)</span>
|
||||
<i class="ri ri-close-line text-2xl" />
|
||||
</button>
|
||||
|
||||
<div class="min-w-[350px] min-h-[400px] py">
|
||||
<div class=" p-4 font-bold" />
|
||||
|
||||
<div
|
||||
id="logs_loader"
|
||||
style="display: none; width: 95vw; height: 95vh;"
|
||||
class="w-full h-full"
|
||||
>
|
||||
<div
|
||||
style=""
|
||||
class="h-full absolute inset-0 flex flex-row items-center grow transition items-center justify-center appear_from_opacity-3 z-30 rounded p-2"
|
||||
>
|
||||
<div class="flex flex-row items-center grow transition items-center justify-center rounded shadow bg-white h-full">
|
||||
<div
|
||||
type-node="loader"
|
||||
class="flex gap-3 items-center justify-center "
|
||||
>
|
||||
<span class="text-slate-600 text-md font-medium">Загрузка</span>
|
||||
<div role="status">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mr-2 w-6 h-6 text-gray-300 animate-spin dark:text-gray-400 fill-slate-700"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 px-4 pb-4">
|
||||
<div class="flex gap-2 items-end">
|
||||
<div class="relative ">
|
||||
<label
|
||||
for=""
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300 text-left "
|
||||
>
|
||||
Название
|
||||
</label>
|
||||
<input
|
||||
id="period_view_name"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
|
||||
placeholder="Название"
|
||||
name=""
|
||||
type="text"
|
||||
value=""
|
||||
disabled=""
|
||||
autocomplete="off"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
id="display_modal_period"
|
||||
phx-hook="TimeConvert"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
id="period_report"
|
||||
style="width: 95vw"
|
||||
phx-hook="MakeTabulator"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Task
|
||||
v-for="_i in tasks"
|
||||
:key="`${_i}_key`"
|
||||
:index="_i"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import Task from './Task.vue'
|
||||
import TaskModal from './TaskModal.vue'
|
||||
|
||||
export default {
|
||||
name: 'Cron',
|
||||
components: {
|
||||
Task,
|
||||
TaskModal,
|
||||
},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
tasks () {
|
||||
return [1,2,3,4]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
20
live_monitor_vue/src/components/5_pages/Devices/index.vue
Normal file
20
live_monitor_vue/src/components/5_pages/Devices/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
20
live_monitor_vue/src/components/5_pages/FileLogs/index.vue
Normal file
20
live_monitor_vue/src/components/5_pages/FileLogs/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
20
live_monitor_vue/src/components/5_pages/FileViewer/index.vue
Normal file
20
live_monitor_vue/src/components/5_pages/FileViewer/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
20
live_monitor_vue/src/components/5_pages/FilesLogs/index.vue
Normal file
20
live_monitor_vue/src/components/5_pages/FilesLogs/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
20
live_monitor_vue/src/components/5_pages/FreePacks/index.vue
Normal file
20
live_monitor_vue/src/components/5_pages/FreePacks/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
20
live_monitor_vue/src/components/5_pages/Fuel/index.vue
Normal file
20
live_monitor_vue/src/components/5_pages/Fuel/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
20
live_monitor_vue/src/components/5_pages/ICCID/index.vue
Normal file
20
live_monitor_vue/src/components/5_pages/ICCID/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
162
live_monitor_vue/src/components/5_pages/LastPacks/index.vue
Normal file
162
live_monitor_vue/src/components/5_pages/LastPacks/index.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-12 gap-6 col-span-12 h-full">
|
||||
<form
|
||||
phx-submit="fetch_pack"
|
||||
class="rounded-md bg-light px-4 pt-4 pb-6 gap-2 shadow lg:col-span-4 xl:col-span-3 2xl:col-span-2 col-span-12"
|
||||
autocomplete="off"
|
||||
>
|
||||
<h1 class="font-medium leading-tight text-xl mb-4 text-slate-600">
|
||||
Последние пакеты
|
||||
</h1>
|
||||
<div class="grid gap-3 mb-3 ">
|
||||
<div class="relative ">
|
||||
<label
|
||||
for="imei"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300 text-left "
|
||||
>
|
||||
imei
|
||||
</label>
|
||||
<input
|
||||
id="selected_imei"
|
||||
required=""
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
|
||||
placeholder="imei"
|
||||
name="imei"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
:value="imei"
|
||||
@input="(e) => setImei(e.target.value)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label
|
||||
for="dt_start"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||
>Дата начала</label>
|
||||
<Datepicker
|
||||
:defaultDate="dtStart"
|
||||
:enableTime="true"
|
||||
:onchange="setDtStart"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label
|
||||
for="dt_finish"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||
>Дата окончания</label>
|
||||
<Datepicker
|
||||
:defaultDate="dtFinish"
|
||||
:enableTime="true"
|
||||
:onchange="setDtFinish"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-3 grid-cols-4">
|
||||
<button
|
||||
type="button"
|
||||
class="col-span-4 lg:col-span-4 cursor-pointer text-white bg-primary hover:brightness-90 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-ssm w-full sm:w-auto px-5 py-1 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 transition_up"
|
||||
@click="uploadData"
|
||||
>
|
||||
Выгрузить
|
||||
</button>
|
||||
<MachinesModal />
|
||||
<ButtonModal btnClass="cursor-pointer col-span-4 bg-transparent hover:bg-primary text-primary font-semibold hover:text-white py-1 text-ssm px-4 border border-primary hover:border-transparent rounded-lg text-center transition_up">
|
||||
<template #button>
|
||||
<div id="question">
|
||||
<i class="ri-question-mark" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="overflow-auto min-w-[70vw] h-[70vh] p-4">
|
||||
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
||||
<span style="font-size: 1.1em; font-weight: bold;">Страница предназначена для получения сведений о последних полученных пакетах от машин<br></span><span style="border-left: 5px solid rgba(0, 0, 0, 0.45); padding: 10px 5px;"><span style="font-weight: bold;">Работа со страницей</span><br><span>Поля выбора находятся на странице. Чтобы загразить данные необходимо -<ol style="font-size: 0.9em; margin-bottom: 0px;"><li>1. ввести пакеты через запятую</li><li>2. ввести imei или выбрать машину/устройство</li><li>3. нажать на кнопку 'загрузить' и дождаться загрузки</li><li style="font-size: 0.9em; color: gray;">(опционально) изменить даты</li></ol></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</ButtonModal>
|
||||
</div>
|
||||
</form>
|
||||
<div class="lg:col-span-8 xl:col-span-9 2xl:col-span-10 col-span-12 bg-light shadow">
|
||||
<div
|
||||
v-if="pageState == 'await'"
|
||||
id="report_packs"
|
||||
class="tab-pane active relative text-center min-h-[300px] relative rounded-md "
|
||||
>
|
||||
<span> Выберите машину и нажмите выгрузить</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="pageState == 'loading'"
|
||||
id="report_packs"
|
||||
class="tab-pane active relative text-center min-h-[300px] relative rounded-md w-full h-full flex items-center justify-center"
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
<div
|
||||
v-if="pageState == 'isLoaded'"
|
||||
id="report_packs"
|
||||
class="tab-pane active relative text-center min-h-[300px] relative rounded-md "
|
||||
>
|
||||
<Tabulator v-bind="tabulatorOtps" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ButtonModal from '@molecules/ButtonModal/index.vue'
|
||||
import Datepicker from "@molecules/Datepicker/index.vue"
|
||||
import {mapGetters, mapMutations, mapActions} from 'vuex'
|
||||
import Tabulator from "@molecules/Tabulator/index.vue"
|
||||
import Spinner from "@molecules/Spinner/index.vue"
|
||||
import MachinesModal from "@organisms/MachinesModal/index.vue"
|
||||
|
||||
export default {
|
||||
name: 'LastPacks',
|
||||
components: {
|
||||
Datepicker,
|
||||
ButtonModal,
|
||||
Tabulator,
|
||||
Spinner,
|
||||
MachinesModal
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
packNum: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('last_packs', ['dtStart', 'dtFinish', 'imei', 'pageState', 'packsData']),
|
||||
tabulatorOtps() {
|
||||
return {
|
||||
dataSource: this.packsData,
|
||||
columns: [
|
||||
{
|
||||
title: "Нормер пакета",
|
||||
field: "pack_number",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Количество записей",
|
||||
field: "count",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Последняя дата пакета",
|
||||
field: "pack_dt",
|
||||
width: 150,
|
||||
render: "{{ dt_format (dt_shift pack_dt 3 'hour') }}"
|
||||
}
|
||||
],
|
||||
height: "550px"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('last_packs', [
|
||||
"setDtStart",
|
||||
"setDtFinish",
|
||||
"setImei"
|
||||
]),
|
||||
...mapActions('last_packs', ['uploadData'])
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
173
live_monitor_vue/src/components/5_pages/LastPacksNum/index.vue
Normal file
173
live_monitor_vue/src/components/5_pages/LastPacksNum/index.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-12 gap-6 col-span-12 h-full">
|
||||
<form
|
||||
phx-submit="fetch_pack"
|
||||
class="rounded-md bg-light px-4 pt-4 pb-6 gap-2 shadow lg:col-span-4 xl:col-span-3 2xl:col-span-2 col-span-12"
|
||||
autocomplete="off"
|
||||
>
|
||||
<h1 class="font-medium leading-tight text-xl mb-4 text-slate-600">
|
||||
Последние пакеты
|
||||
</h1>
|
||||
<div class="grid gap-3 mb-3 ">
|
||||
<div class="relative ">
|
||||
<label
|
||||
for="imei"
|
||||
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300 text-left "
|
||||
>
|
||||
Номер пакета
|
||||
</label>
|
||||
<input
|
||||
id="selected_imei"
|
||||
required=""
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full py-1 pl-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 "
|
||||
placeholder="Номер пакета"
|
||||
name="imei"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
:value="imei"
|
||||
@input="(e) => setImei(e.target.value)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-3 grid-cols-4">
|
||||
<button
|
||||
type="button"
|
||||
class="col-span-4 lg:col-span-4 cursor-pointer text-white bg-primary hover:brightness-90 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-ssm w-full sm:w-auto px-5 py-1 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 transition_up"
|
||||
@click="uploadData"
|
||||
>
|
||||
Выгрузить
|
||||
</button>
|
||||
<ButtonModal btnClass="cursor-pointer col-span-4 bg-transparent hover:bg-primary text-primary font-semibold hover:text-white py-1 text-ssm px-4 border border-primary hover:border-transparent rounded-lg text-center transition_up">
|
||||
<template #button>
|
||||
<div id="question">
|
||||
<i class="ri-question-mark" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="overflow-auto min-w-[70vw] h-[70vh] pt-10 px-4">
|
||||
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
||||
<span style="font-size: 1.1em; font-weight: bold;">Страница предназначена для получения сведений о последних полученных пакетах, в результате выдающая список машин, которые имели данный пакет за 3х месячный период (иначе запрос становится слишком долгим)<br></span><span style="border-left: 5px solid rgba(0, 0, 0, 0.45); padding: 10px 5px;"><span style="font-weight: bold;">Работа со страницей</span><br><span>Поля выбора находятся на странице. Чтобы загразить данные необходимо -<ol style="font-size: 0.9em; margin-bottom: 0px;"><li>1. ввести номер пакета</li><li>2. нажать на кнопку 'загрузить' и дождаться загрузки</li></ol></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</ButtonModal>
|
||||
</div>
|
||||
</form>
|
||||
<div class="lg:col-span-8 xl:col-span-9 2xl:col-span-10 col-span-12 bg-light shadow">
|
||||
<div
|
||||
v-if="pageState == 'await'"
|
||||
id="report_packs"
|
||||
class="tab-pane active relative text-center min-h-[300px] relative rounded-md "
|
||||
>
|
||||
<span> Выберите машину и нажмите выгрузить</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="pageState == 'loading'"
|
||||
id="report_packs"
|
||||
class="tab-pane active relative text-center min-h-[300px] relative rounded-md w-full h-full flex items-center justify-center"
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
<div
|
||||
v-if="pageState == 'isLoaded'"
|
||||
id="report_packs"
|
||||
class="tab-pane active relative text-center min-h-[300px] relative rounded-md "
|
||||
>
|
||||
<Tabulator v-bind="tabulatorOtps" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ButtonModal from '@molecules/ButtonModal/index.vue'
|
||||
import {mapGetters, mapMutations, mapActions} from 'vuex'
|
||||
import Tabulator from "@molecules/Tabulator/index.vue"
|
||||
import Spinner from "@molecules/Spinner/index.vue"
|
||||
|
||||
export default {
|
||||
name: 'LastPacksNum',
|
||||
components: {
|
||||
ButtonModal,
|
||||
Tabulator,
|
||||
Spinner,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
packNum: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('last_packs_num', ['packNum', 'pageState', 'packsData']),
|
||||
tabulatorOtps() {
|
||||
console.log(this.packsData)
|
||||
|
||||
return {
|
||||
dataSource: this.packsData,
|
||||
columns: [
|
||||
{
|
||||
title: "Нормер пакета",
|
||||
field: "pack_number",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Последняя дата пакета",
|
||||
field: "pack_dt",
|
||||
width: 150,
|
||||
render: "{{ dt_format (dt_shift pack_dt 3 'hour') }}"
|
||||
},
|
||||
{
|
||||
title: "Тип/Модификация",
|
||||
field: "machine_type",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Номер",
|
||||
field: "zav_nomer",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "id машины",
|
||||
field: "machine_id",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "id устройства",
|
||||
field: "device_id",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "IMEI",
|
||||
field: "imei",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Дорога",
|
||||
field: "railway_name",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Предприятие",
|
||||
field: "org_name",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Номер 8 зн.",
|
||||
field: "nomer_zn8",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Номер устройства",
|
||||
field: "device_number",
|
||||
width: 150
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('last_packs_num', [
|
||||
"setPackNum"
|
||||
]),
|
||||
...mapActions('last_packs_num', ['uploadData'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,49 @@
|
||||
<script>
|
||||
import {mapGetters, mapActions, mapMutations, useStore} from "vuex"
|
||||
import Button from "@/components/1_atoms/Button.vue"
|
||||
import Tabulator from "@molecules/Tabulator/index.vue"
|
||||
|
||||
export default {
|
||||
name: 'AddUsers',
|
||||
components: {
|
||||
Button,
|
||||
Tabulator
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
store.dispatch('add_users/uploadData')
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('add_users', ['adminData']),
|
||||
},
|
||||
unmounted() {
|
||||
const store = useStore()
|
||||
store.dispatch('add_users/resetStore')
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
{{ console.log('adminData', adminData)
|
||||
}}
|
||||
<div class="rounded w-full pt-4 pb-1 bg-light cursor-pointer transition-all">
|
||||
<div class="mx-2 mb-3">
|
||||
<a :href="`/html/admin/manage/new?prev_page=/\?page_display_mode=users`">
|
||||
<Button title="Создать" classBtn="cursor-pointer text-white bg-primary hover:brightness-90 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-ssm w-full px-5 py-1 text-center transition-all " />
|
||||
</a>
|
||||
</div>
|
||||
<div id="admin_list" class="tab-pane m-4 active bg-light relative col-span-12 text-center min-h-[300px] rounded-md tabulator">
|
||||
<Tabulator v-bind="adminData" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,105 @@
|
||||
<script>
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex"
|
||||
import Spinner from "@molecules/Spinner/index.vue"
|
||||
import {PushAfterTimeout} from '@store/hooks/PushAfterTimeout'
|
||||
import Accordion from '@atoms/Accordion.vue'
|
||||
|
||||
export default {
|
||||
name: 'FinderPacks',
|
||||
components: {
|
||||
Spinner,
|
||||
Accordion
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
store.dispatch('finder_packs/uploadData')
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('finder_packs', ['packsGroups', 'searchValue', 'isOpen']),
|
||||
},
|
||||
unmounted() {
|
||||
const store = useStore();
|
||||
store.dispatch('finder_packs/resetStore')
|
||||
},
|
||||
methods: {
|
||||
...mapActions('finder_packs', ['updateSearch']),
|
||||
updateSearchValue: function(e) {
|
||||
const searchInput = new PushAfterTimeout('searchInput', 1500, 'right-[25px] top-[53%]')
|
||||
if (searchInput.input) {
|
||||
setTimeout(() => {
|
||||
this.updateSearch(e.target.value)
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div class="rounded w-full transition-all min-h-[500px] relative">
|
||||
<div v-if="!packsGroups" id="dataLoader" class="h-full absolute inset-0 flex flex-row items-center grow transition items-center justify-center bg-light appear_from_opacity-3 z-30 rounded p-2">
|
||||
<Spinner style="padding: 0"/>
|
||||
</div>
|
||||
<div v-if="packsGroups" phx-change="update_pack_filter" class="p-5 flex flex-col gap-4 bg-light relative items-center">
|
||||
<div id="searchInput" class="w-full flex flex-col gap-2" >
|
||||
<div class="font-bold">Поиск (название и номер): </div>
|
||||
<input v-bind:value="searchValue" v-on:change="updateSearchValue" :status="status" class="pl-2 py-1 rounded-r-md w-full max-h-[29px] text-sm border-none focus-visible:outline-blue-300" placeholder="Название и номер через пробел"/>
|
||||
</div>
|
||||
<div class="border-2 rounded-lg w-full bottom-0"></div>
|
||||
</div>
|
||||
<div v-if="packsGroups?.length === 0" id="no_data" class="w-full min-h-[80vh] bg-light dark:bg-slate-900 dark:text-slate-300 relative">
|
||||
<div class="flex items-center justify-center text-lg font-bold pt-5">Пакеты не найдены</div>
|
||||
</div>
|
||||
<div class="bg-light">
|
||||
<div v-if="packsGroups?.length > 0" class="grid grid-cols-12 gap-3 p-4">
|
||||
<div :key="idx" v-for="(group, idx) in packsGroups" class="col-span-12 sm:col-span-6 lg:col-span-4">
|
||||
<Accordion :isOpen="isOpen" :id="`pack_group_${group.pack_group_id}`">
|
||||
<template #header>
|
||||
<div v-if="group.order_index" id="packsInfoTitle" class="font-bold text-sm">
|
||||
<span class="text-slate-500 font-bold">
|
||||
{{ group.order_index }}.
|
||||
</span>
|
||||
<span class="font-bold">
|
||||
{{ group.title }}
|
||||
</span></div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-2">
|
||||
<table >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="items-center justify-center pb-2">Название</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="struct in group.pack_structs" >
|
||||
<td class="items-center justify-center py-2">
|
||||
<div class="flex gap-2 w-full">
|
||||
<div class="w-full text-left decoration-dashed cursor-pointer hover:text-slate-800 transition-all">
|
||||
{{ struct.name }}
|
||||
</div>
|
||||
<a class="bg-slate-500 hover:bg-slate-700 text-white text-xs font-bold py-1 px-3 rounded transition-all h-fit" target="_blank" :href="`/html/packs/${group.link}?pack=${struct.name}`">
|
||||
таблица
|
||||
</a>
|
||||
<a class="bg-slate-500 hover:bg-slate-700 text-white text-xs font-bold py-1 px-3 rounded transition-all h-fit" target="_blank" :href="`/admin_panel/packs/${group.pack_group_id}/${struct.id}`">карточка</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
129
live_monitor_vue/src/components/5_pages/Main/Machines/Charts.vue
Normal file
129
live_monitor_vue/src/components/5_pages/Main/Machines/Charts.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {ref, toRaw, computed} from 'vue'
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex";
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import Button from "@/components/1_atoms/Button.vue";
|
||||
import DoubleSwitch from "@/components/1_atoms/DoubleSwitch.vue";
|
||||
import Chart from "@/components/2_molecules/Chart/index.vue";
|
||||
import ECharts from '@store/hooks/Echarts';
|
||||
|
||||
export default {
|
||||
name: 'Charts',
|
||||
components: {
|
||||
Button,
|
||||
Chart,
|
||||
DoubleSwitch
|
||||
},
|
||||
setup() {
|
||||
const route = useRoute()
|
||||
const path = computed(() => route.path)
|
||||
const query = computed(() => route.query)
|
||||
const typeCharts = path.value === '/' && query?.value?.mode && query?.value?.type ? query?.value?.type : ''
|
||||
const isChartTypesUrl = typeCharts === 'main' ? false : true
|
||||
return {
|
||||
isChartTypesUrl
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isChartTypes: this.isChartTypesUrl,
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
isChecked: computed(() => this.isChartTypes),
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeChartData: {
|
||||
handler(newValue, oldValue) {
|
||||
console.log('newValue - activeChartData', newValue)
|
||||
if (newValue.id && newValue.type && newValue.data) {
|
||||
const chart = new ECharts(newValue.id)
|
||||
const isSelectedLegend = this.legendFiltersParams.length > 0 && this.legendFiltersParams[0].id === newValue.id
|
||||
console.log('this.legendFiltersParams', this.legendFiltersParams)
|
||||
newValue.data.legendFilterData = isSelectedLegend ? this.legendFiltersParams : []
|
||||
this.setTypeFunc(newValue.type, chart, newValue.data)
|
||||
}
|
||||
},
|
||||
// deep: true
|
||||
},
|
||||
activeMenuChartData: {
|
||||
handler(newValue, oldValue) {
|
||||
// console.log('newValue - activeMenuChartData', newValue)
|
||||
if (newValue.groupByInfo && newValue.groupByInfo.chartId) {
|
||||
const chart = new ECharts(newValue.groupByInfo.chartId)
|
||||
chart.setMenuChart(newValue)
|
||||
if (newValue.groupByInfo.isDownloadChart) {
|
||||
chart.downloadChart(newValue, this.updateCharts)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('machines', ['selectedSelects', 'selectsData', 'toggleFilter', 'chartsData', 'activeChartData', 'prevGroupByData', 'activeMenuChartData', 'openMenuChart', 'legendFiltersParams']),
|
||||
},
|
||||
mounted () {
|
||||
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('machines', ['setSelectedSelect', 'setToggleFilter']),
|
||||
...mapActions('machines', ['updateSelects', 'updateCharts']),
|
||||
updateSelectedSelects: function(value) {
|
||||
if (value.length > 0) {
|
||||
this.updateSelects({key: 'set', value: value})
|
||||
}
|
||||
},
|
||||
removedOption: function(value) {
|
||||
this.updateSelects({key: 'removed', value: value})
|
||||
},
|
||||
onToggleCharts: function(value) {
|
||||
console.log('onToggleCharts', value)
|
||||
const path = this.$route.path
|
||||
const query = this.$route.query
|
||||
const chartsType = value ? 'types' : 'main'
|
||||
this.$router.push({ path: path, query: {...query, type: chartsType} })
|
||||
this.isChartTypes = value
|
||||
},
|
||||
setTypeFunc: function(type, chart, data) {
|
||||
switch(type) {
|
||||
case 'pie': return chart.makePieCharts({...data, type: type})
|
||||
case 'mount': return chart.makeBarMountCharts({...data, type: type, height: this.height})
|
||||
case 'mainMl': return chart.makeBarCharts({...data, type: type})
|
||||
case 'default': return chart.makeBarTypesCharts({...data, type: type})
|
||||
case 'types': return chart.makeBarTypesCharts({...data, type: type})
|
||||
case 'without_first_layout': return chart.makeBarTypesCharts({...data, type: type})
|
||||
case 'ml': return chart.makeBarTypesCharts({...data, type: type})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div class="px-2 sm:px-5 pt-1 bg-light">
|
||||
<div class="relative">
|
||||
<div class="doubleSwitch flex justify-end mt-3 mb-2">
|
||||
<DoubleSwitch name="chartsView" @switched="onToggleCharts" firstColor="#8b5cf6" secondColor="#2563eb" firstTitle="Общий вид" secondTitle="По типам" />
|
||||
</div>
|
||||
<div v-if="!isChartTypes" id="main" class="relative flex flex-wrap justify-center w-full h-full mb-3" >
|
||||
<div v-for="(chart, idx) in chartsData.main">
|
||||
<Chart :idx="idx" :id="chart.id" :type="chart.type" :height="chart.height" :maxHeight="chart.maxHeight" :data="chart.data"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isChartTypes" id="types" class="relative flex flex-wrap justify-center w-full h-full mb-3" >
|
||||
<div v-for="(chart, idx) in chartsData.types">
|
||||
<Chart :idx="idx" :id="chart.id" :type="chart.type" :height="chart.height" :maxHeight="chart.maxHeight" :data="chart.data"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
@@ -0,0 +1,170 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex"
|
||||
import Modal from '@molecules/Modal/index.vue'
|
||||
import Accordion from '@atoms/Accordion.vue'
|
||||
import PackView from '@organisms/PackView/index.vue'
|
||||
import Spinner from "@molecules/Spinner/index.vue"
|
||||
import {getMachineInfo} from './helpers'
|
||||
|
||||
export default {
|
||||
name: 'DataModal',
|
||||
components: {
|
||||
Modal,
|
||||
Accordion,
|
||||
PackView,
|
||||
Spinner
|
||||
},
|
||||
props: {
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
close: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
headerClass: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
machineMainInfo: null,
|
||||
machineOverInfo: null
|
||||
}
|
||||
},
|
||||
updated () {
|
||||
this.$nextTick(function () {
|
||||
if (this.data && this.id && this.open) {
|
||||
// const newMap = new CoordsMap(this.id)
|
||||
// newMap.loadMap(this.data.coords, this.data.machine)
|
||||
console.log('this.data', this.data)
|
||||
this.machineMainInfo = getMachineInfo(this.machineInfo.machine_info, true)
|
||||
this.machineOverInfo = getMachineInfo(this.machineInfo.machine_info, false)
|
||||
}
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('machines', ['machineInfo', 'reportPacks']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions('machines', ['loadPack']),
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
v-if="open"
|
||||
:close="close"
|
||||
:isOpen="true"
|
||||
>
|
||||
<div class="border-b p-4 font-bold">
|
||||
Последние данные
|
||||
</div>
|
||||
{{ console.log('reportPacks', reportPacks)
|
||||
}}
|
||||
<div :id="id" class="w-[95vw] h-[85vh] m-auto p-5">
|
||||
<div v-if="!machineInfo" id="dataLoader" class=" h-full flex justify-center items-center">
|
||||
<Spinner style="padding: 0"/>
|
||||
</div>
|
||||
<div v-if="machineInfo" class="w-full flex flex-col gap-5">
|
||||
<div class="w-full">
|
||||
<Accordion id="machineInfo">
|
||||
<template #header>
|
||||
<div v-if="machineInfo?.machine_name" id="machineInfoTitle" class="font-bold text-xl">{{ machineInfo.machine_name }}</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div v-if="machineInfo.machine_info?.length > 0" id="machineInfoBody" class="flex w-full justify-start 2xl:justify-between gap-3 flex-wrap">
|
||||
<div class="flex p-4">
|
||||
<div class="flex mr-8 flex-col bg-slate-200 p-3 rounded">
|
||||
<h2 clas="font-bold flex items-center"><i class="ri-flashlight-line mr-1" /> Основная информация</h2>
|
||||
<div class="mt-2 p-2 rounded-md flex flex-col bg-slate-300">
|
||||
<div v-if="machineMainInfo?.length > 0" class="flex flex-col gap-2">
|
||||
<div :key="idx" v-for="(info, idx) in machineMainInfo" class="flex items-center items-center bg-slate-200 px-3 py-2 rounded-md">
|
||||
<div class="text-slate-700 font-bold mr-2">{{ info.title }}</div>
|
||||
<div class="text-slate-500">{{ info.val }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="min-width: 300px;" class="flex flex-col bg-slate-200 p-3 rounded">
|
||||
<h2 clas="font-bold flex items-center"><i class="ri-git-pull-request-line mr-1"></i> Дополнительно</h2>
|
||||
<div class="mt-2 p-2 rounded-md flex flex-col bg-slate-300">
|
||||
<div v-if="machineOverInfo?.length > 0" class="flex flex-col gap-2">
|
||||
<div :key="idx" v-for="(info, idx) in machineOverInfo" class="flex items-center items-center bg-slate-200 px-3 py-2 rounded-md">
|
||||
<div class="text-slate-700 font-bold mr-2">{{ info.title }}</div>
|
||||
<div class="text-slate-500">{{ info.val }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Accordion>
|
||||
</div>
|
||||
<Accordion :isOpen="true" id="machine_packs">
|
||||
<template #header>
|
||||
<div class="font-bold text-xl">Пакеты</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div id="packReportButtons">
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="grid grid-cols-12 gap-3">
|
||||
<div v-if="reportPacks.length === 0" class="flex items-center justify-center text-slate-700 text-2xl font-bold col-span-12">
|
||||
<h2>Нет данных по машине</h2>
|
||||
</div>
|
||||
<div :key="idx" v-if="reportPacks.length > 0" v-for="(pack, idx) in reportPacks" class="col-span-6 sm:col-span-4 lg:col-span-3 xl:col-span-2 flex items-start flex gap-1 justify-center rounded-md bg-slate-300">
|
||||
<div class="flex-col flex-grow flex py-1">
|
||||
<div class="flex text-slate-700 font-medium px-3 text-slate-700">
|
||||
<div class="mr-1">Номер</div>
|
||||
<div>{{ pack.pack_number }}</div>
|
||||
</div>
|
||||
<div class="text-slate-600 flex px-3 text-slate-600">
|
||||
<div class="mr-1">Количество</div>
|
||||
<div>{{ pack.count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="loadPack(pack.pack_number)" class="h-full flex items-center justify-center text-white rounded-md px-3 text-center cursor-pointer transitiona-all bg-slate-400 hover:bg-slate-600">
|
||||
<i class="ri-eye-line"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full h-[1px] bg-slate-300"></div>
|
||||
<PackView :isLoader="false" id="machineReport">
|
||||
<template #description>
|
||||
<span class="text-slate-400 text-sm">последние 30</span>
|
||||
</template>
|
||||
<template #error>
|
||||
Слишком много пакетов для запроса
|
||||
</template>
|
||||
</PackView>
|
||||
</div>
|
||||
</template>
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,28 @@
|
||||
const getMachineInfo = (info, isMain) => {
|
||||
const mainInfo = ["Текущая симкарта", "Последняя коммуникация", "Предприятие приписки", "Текущее устройство"]
|
||||
if (isMain) {
|
||||
return info.reduce((acc, el, idx) => {
|
||||
if (mainInfo.includes(Object.keys(el)[0])) {
|
||||
const item = {title: Object.keys(el)[0], val: Object.values(el)[0]}
|
||||
acc.push(item)
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
if (!isMain) {
|
||||
return info.reduce((acc, el, idx) => {
|
||||
if (!mainInfo.includes(Object.keys(el)[0])) {
|
||||
const item = {title: Object.keys(el)[0], val: Object.values(el)[0]}
|
||||
acc.push(item)
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { getMachineInfo }
|
||||
@@ -0,0 +1,71 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {ref, toRaw, computed} from 'vue'
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex";
|
||||
import Button from "@/components/1_atoms/Button.vue";
|
||||
import VueMultiselect from 'vue-multiselect'
|
||||
|
||||
export default {
|
||||
name: 'Filters',
|
||||
components: {
|
||||
Button,
|
||||
VueMultiselect
|
||||
},
|
||||
setup() {
|
||||
// const store = useStore()
|
||||
// store.dispatch('main/updateSelects')
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpenOptions: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('machines', ['selectedSelects', 'selectsData', 'toggleFilter', 'selectedData']),
|
||||
},
|
||||
mounted () {
|
||||
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('machines', ['setSelectedSelect', 'setToggleFilter']),
|
||||
...mapActions('machines', ['updateSelects']),
|
||||
updateSelectedSelects: function(value) {
|
||||
if (value.length > 0) {
|
||||
this.updateSelects({key: 'set', value: value})
|
||||
}
|
||||
},
|
||||
removedOption: function(value) {
|
||||
this.updateSelects({key: 'removed', value: value})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div id="ignore:2Qxwqz" class="pb-1">
|
||||
{{ console.log('selectedData' ,selectedData)
|
||||
}}
|
||||
<div v-if="toggleFilter" wrapper_class="grid grid-cols-12 gap-2" selects_class="col-span-12 sm:col-span-6 lg:col-span-3" class="grid grid-cols-12 gap-2 mb-3 border-t border-slate-300 pt-3" fields={@filters_fields} id="SelectFilters" phx-hook="SelectFilters">
|
||||
<div v-for="selectData in selectsData" class="col-span-12 sm:col-span-6 lg:col-span-3">
|
||||
<VueMultiselect
|
||||
:multiple="true"
|
||||
v-model="selectedSelects[selectData.key]"
|
||||
label="name"
|
||||
track-by="name"
|
||||
:options="selectData.data"
|
||||
:placeholder="selectData.title"
|
||||
@update:model-value="updateSelectedSelects"
|
||||
@remove="removedOption"
|
||||
:showLabels="false"
|
||||
>
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<script>
|
||||
import {mapGetters, mapMutations, useStore} from "vuex"
|
||||
import Tabulator from "@molecules/Tabulator/index.vue"
|
||||
import { EventStore } from "@store/hooks/EventStore"
|
||||
|
||||
export default {
|
||||
name: 'HistoryViews',
|
||||
components: {Tabulator},
|
||||
setup () {
|
||||
const initLocalStorage = new EventStore()
|
||||
const historyData = initLocalStorage.localStoreGetData({key: 'historyViews', onlyGet: true})
|
||||
const isHistoryData = historyData && historyData.length > 0
|
||||
// const updatedData = historyData.filter((el, idx, arr) => arr.findIndex((item) => item.machine_id === el.machine_id) === idx).slice(0, 50) // uniq values
|
||||
const updatedData = isHistoryData ? historyData.slice(0, 50) : []
|
||||
const store = useStore()
|
||||
store.commit('machines/setHistoryData', updatedData)
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('machines', ['historyData']),
|
||||
tabulatorOtps() {
|
||||
return {
|
||||
dataSource: this.historyData,
|
||||
columns: [
|
||||
{
|
||||
title: "ID машины",
|
||||
field: "machine_id",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Тип машины",
|
||||
field: "machine_type",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Карточка машины",
|
||||
field: "link",
|
||||
log: true,
|
||||
width: 180,
|
||||
render: `<div class="flex items-center gap-2">
|
||||
<a target="_blank" class="underline decoration-dashed" href="{{ link }}">Открыть карточку машины</i></a>
|
||||
</div>`
|
||||
},
|
||||
{
|
||||
title: "Время просмотра",
|
||||
field: "created_at",
|
||||
width: 150
|
||||
}
|
||||
],
|
||||
height: "550px"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('machines', ['setHistoryData']),
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tabulator v-bind="tabulatorOtps" />
|
||||
</template>
|
||||
@@ -0,0 +1,121 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex"
|
||||
import Button from "@/components/1_atoms/Button.vue"
|
||||
import { EventStore } from "@store/hooks/EventStore"
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
name: 'Machine',
|
||||
components: {
|
||||
Button,
|
||||
},
|
||||
props: {
|
||||
machine: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
openMap: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
openData: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
|
||||
return {
|
||||
initLocalStorage: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('machines', ['machinesData', 'historyData', 'historyMachines']),
|
||||
},
|
||||
mounted () {
|
||||
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('machines', ['setInitHistory', 'setHistoryData', 'setHistoryMachines']),
|
||||
...mapActions('machines', ['openMapModal', 'openDataModal']),
|
||||
buttonClass: function(value) {
|
||||
return `border border-slate-400 flex items-center justify-center cursor-pointer rounded transition-all text-xs text-center py-[5px] px-2 ${value}`
|
||||
},
|
||||
cardFunc: function(value) {
|
||||
this.initLocalStorage = new EventStore()
|
||||
if (this.machine.machine_id) {
|
||||
const valueStorage = {machine_id: this.machine.machine_id, machine_type: this.machine.machine_type, link: `/html/askr_devices/analyze_device/${this.machine.device_number}`, created_at: moment().format('HH:mm:ss DD-MM-YY')}
|
||||
return this.initLocalStorage.localStoreGetData({key: 'historyViews', newData: valueStorage}, this.updateLocalData)
|
||||
}
|
||||
},
|
||||
updateLocalData: function(value) {
|
||||
const historyData = value.data ? value.data : []
|
||||
const mergedHistory = value.newData !== 'mount' ? [value.newData, ...historyData] : historyData
|
||||
// const updatedHistory = mergedHistory.filter((el, idx, arr) => arr.findIndex((item) => item.machine_id === el.machine_id) === idx).slice(0, 50) // uniq values
|
||||
const updatedHistory = mergedHistory.slice(0, 50)
|
||||
const initHistory = (Array.isArray(value.newData) && value.newData.length > 0 || value.newData === 'mount') ? true : false
|
||||
this.setInitHistory(initHistory)
|
||||
this.setHistoryData(updatedHistory)
|
||||
return this.initLocalStorage.localStoreSetData({key: 'historyViews', value: updatedHistory})
|
||||
},
|
||||
mapFunc: function(machine) {
|
||||
// console.log('machine', machine)
|
||||
this.openMapModal(machine)
|
||||
return this.openMap()
|
||||
},
|
||||
dataFunc: function(machine) {
|
||||
// console.log('machine', machine)
|
||||
this.openDataModal(machine)
|
||||
return this.openData()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div id="card" class="pt-3 border w-[23%] min-w-[310px] border-slate-300 rounded transition-all bg-slate-300}">
|
||||
<div class="h-full flex flex-col gap-5 relative text-left justify-between">
|
||||
<div class="flex flex-col items-end absolute text-white top-[-0.75rem] right-0 h-full min-w-[173px] p-2" style="backdrop-filter: blur(10px);">
|
||||
<div class="mb-2 text-right drop-shadow" search-mode="machine_id">ID: {{ machine?.machine_id }}</div>
|
||||
<div class="flex flex-row">
|
||||
<span>{{ machine.railway_name }}</span><i class="ri-route-line ml-2" title="Дорога:"/>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<span>{{ machine.org_name }}</span><i class="ri-building-2-line ml-2" title="Компания:"/>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<span>{{ machine.nomer_zn8 }}</span><i class="ri-number-8 ml-2" title="8зн номер:"/>
|
||||
</div>
|
||||
<div class="flex flex-row" search-mode="device_number">
|
||||
<span>{{ machine.device_number }}</span><i class="ri-rss-line ml-2" title="Устройство:"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col w-full mt-4 mb-2 pl-4 mt-1 z-[1]">
|
||||
<div class="text-slate-800 font-bold m-0" search-mode="machine_type">{{ machine.machine_type }}</div>
|
||||
<div :machine_id="machine.machine_id" history_click="/html/askr_devices/analyze_comm/#{@machine?.imei}" class="text-slate-500 text-xs underline-offset-4" search-mode="imei">{{ machine.imei }}</div>
|
||||
</div>
|
||||
<div class="flex lg:flex-row flex-col gap-1 my-2 px-2 z-[1]">
|
||||
<div class="flex">
|
||||
<a :href="`/html/askr_devices/analyze_device/${machine.device_number}`" id="to_card_machine" no-init="false" @click="cardFunc(machine)" target="_blank" :class="buttonClass('bg-primary text-white')">Карточка</a>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<div @click="mapFunc(machine)" :class="buttonClass('bg-white/25 hover:bg-slate-400 hover:text-white')"><i class="ri-map-pin-line"/></div>
|
||||
<div @click="dataFunc(machine)" :class="buttonClass('bg-white/25 hover:bg-slate-400 hover:text-white')">30</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#card {
|
||||
background: linear-gradient(96.95deg,rgba(236,236,243,.95) 1.45%,hsla(0,0%,100%,.703) 20.43%,hsla(0,0%,100%,0) 98.58%),linear-gradient(277.71deg,rgba(0,8,38,.3),rgba(0,5,20,.076) 45.46%,hsla(0,0%,100%,0) 97.87%),linear-gradient(0deg,rgba(123,134,152,.7),rgba(123,134,152,.7));
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,241 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {computed} from 'vue'
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex"
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import Button from "@/components/1_atoms/Button.vue"
|
||||
import VueMultiselect from 'vue-multiselect'
|
||||
import Filters from "./Filters.vue"
|
||||
import Machine from "./Machine.vue"
|
||||
import Charts from "./Charts.vue"
|
||||
import HistoryViews from "./HistoryViews.vue"
|
||||
import Table from "./Table.vue"
|
||||
import MapModal from '@molecules/MapModal/index.vue'
|
||||
import DataModal from "./DataModal/DataModal.vue"
|
||||
import Spinner from "@molecules/Spinner/index.vue"
|
||||
import {PushAfterTimeout} from '@store/hooks/PushAfterTimeout'
|
||||
import {EventStore} from '@store/hooks/EventStore'
|
||||
|
||||
export default {
|
||||
name: 'Machines',
|
||||
components: {
|
||||
Button,
|
||||
Filters,
|
||||
VueMultiselect,
|
||||
Machine,
|
||||
Table,
|
||||
Charts,
|
||||
HistoryViews,
|
||||
MapModal,
|
||||
DataModal,
|
||||
Spinner
|
||||
},
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
default: 'static',
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
openMap: false,
|
||||
openData: false,
|
||||
}
|
||||
},
|
||||
setup () {
|
||||
const initLocalStorage = new EventStore()
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const path = computed(() => route.path)
|
||||
const query = computed(() => route.query)
|
||||
|
||||
const historyData = initLocalStorage.localStoreGetData({key: 'historyViews', onlyGet: true})
|
||||
const isHistoryData = historyData && historyData.length > 0
|
||||
const updatedData = isHistoryData ? historyData.filter((el, idx, arr) => arr.findIndex((item) => item.machine_id === el.machine_id) === idx).slice(0, 20) : []
|
||||
|
||||
const urlParams = path.value === '/' && query?.value?.mode ? query.value.mode : ''
|
||||
const externalParams = {historyData: updatedData, urlParams: urlParams}
|
||||
|
||||
store.commit('machines/setHistoryData', updatedData)
|
||||
store.dispatch('machines/uploadData', externalParams)
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('machines', ['searchModes', 'searchValue', 'selectedSearchMode', 'leftTopButtons', 'rightTopButtons', 'selectedMode', 'toggleFilter', 'activeFilterBtn', 'mapData', 'machinesData']),
|
||||
...mapGetters('layout', ['isOpenMenu']),
|
||||
},
|
||||
mounted () {
|
||||
|
||||
},
|
||||
updated () {
|
||||
this.$nextTick(function () {
|
||||
const path = this.$route.path
|
||||
const query = this.$route.query
|
||||
if (path === '/' && query?.mode) {
|
||||
this.setSelectedMode(query?.mode)
|
||||
}
|
||||
if (path === '/' && !query?.mode) {
|
||||
this.setSelectedMode('cards')
|
||||
}
|
||||
})
|
||||
},
|
||||
unmounted() {
|
||||
const store = useStore();
|
||||
store.dispatch('machines/resetStore')
|
||||
},
|
||||
watch: {
|
||||
isSuccessSearch: {
|
||||
handler(newValue, oldValue) {
|
||||
console.log('isSuccessSearch', newValue, oldValue)
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('machines', ['setSearchMode', 'setSelectedMode', 'setToggleFilter', 'setActiveFilterBtn']),
|
||||
...mapActions('machines', ['updateSearch', 'clearFilters']),
|
||||
updateSearchMode: function(selectedSearchMode) {
|
||||
this.setSearchMode(selectedSearchMode)
|
||||
},
|
||||
updateSearchValue: function(e) {
|
||||
const searchInput = new PushAfterTimeout('searchInput', 1500)
|
||||
if (searchInput.input) {
|
||||
setTimeout(() => {
|
||||
this.updateSearch(e.target.value)
|
||||
}, 1500)
|
||||
|
||||
}
|
||||
},
|
||||
setFilteredAction (filteredAction) {
|
||||
this.setActiveFilterBtn(filteredAction)
|
||||
if (filteredAction === 'filter') {
|
||||
this.setToggleFilter()
|
||||
}
|
||||
if (filteredAction ==='clearFilter') {
|
||||
this.clearFilters()
|
||||
}
|
||||
},
|
||||
updateMode: function(mode) {
|
||||
if (mode) {
|
||||
const path = this.$route.path
|
||||
const params = mode === 'charts' ? {mode: mode, type: 'main'} : {mode: mode}
|
||||
this.$router.push({ path: path, query: params })
|
||||
return this.setSelectedMode(mode)
|
||||
}
|
||||
},
|
||||
toggleMap () {
|
||||
this.openMap = !this.openMap
|
||||
},
|
||||
toggleData () {
|
||||
this.openData = !this.openData
|
||||
},
|
||||
closeMap () {
|
||||
return this.openMap = !this.openMap
|
||||
},
|
||||
closeData () {
|
||||
return this.openData = !this.openData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div class="px-2 sm:px-5 pt-1 bg-light">
|
||||
<div class="relative">
|
||||
<div class="grid grid-cols-12 gap-4 md:gap-3 lg:gap-4 pb-3">
|
||||
<div class="flex items-center col-span-12 sm:col-span-8 lg:col-span-5 xl:col-span-4 2xl:col-span-3">
|
||||
<div id="search" class="relative w-full max-w-[150px] h-[31px] pl-1 py-1 bg-white border border-primary rounded-l-md z-30">
|
||||
<VueMultiselect
|
||||
v-model="selectedSearchMode"
|
||||
label="name"
|
||||
:options="searchModes"
|
||||
:searchable="false"
|
||||
placeholder=""
|
||||
:showLabels="false"
|
||||
@update:model-value="updateSearchMode"
|
||||
>
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
<div id="searchInput" class="relative w-full border-r border-t border-b border-primary rounded-r-md">
|
||||
<input v-bind:value="searchValue" v-on:change="updateSearchValue" :status="status" class="pl-1 py-1 rounded-r-md w-full max-h-[29px] border-none focus-visible:outline-blue-300"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- {{ console.log('machinesData', machinesData) }} -->
|
||||
<ul id="machinesMode" class="flex col-span-6 sm:col-span-4 md:col-span-4 lg:col-span-3 xl:col-span-2 justify-center">
|
||||
<li v-bind:key="idx" v-for="(mode, idx) in leftTopButtons">
|
||||
<Button :onClick="updateMode" :selected="mode.key" :classIcon="mode.class" :classBtn="[mode.key === 'cards' ? 'rounded-r-none' : 'rounded-l-none', selectedMode === mode.key ? 'text-white bg-primary focus:ring-4 focus:outline-none focus:ring-blue-300' : 'bg-transparent', 'w-full min-w-[88px] h-[30px] border border-primary rounded-md']" />
|
||||
</li>
|
||||
</ul>
|
||||
<div id="historyMode" class="flex col-span-6 sm:col-span-4 md:col-span-4 lg:col-span-3 xl:col-span-2 justify-center">
|
||||
<Button :onClick="updateMode" selected="history" title="Просмотры" :classBtn="[selectedMode === 'history' ? 'focus:ring-4 focus:outline-none focus:ring-blue-300' : '', 'w-full min-w-[88px] h-[30px] bg-primary text-white border border-primary rounded-md']" />
|
||||
</div>
|
||||
<div id="chartsMode" class="flex col-span-6 sm:col-span-4 md:col-span-4 lg:col-span-3 xl:col-span-2 justify-center">
|
||||
<Button :onClick="updateMode" selected="charts" classIcon="ri-pie-chart-2-line" :classBtn="[selectedMode === 'charts' ? 'focus:ring-4 focus:outline-none focus:ring-blue-300' : '', 'w-full min-w-[88px] h-[30px] bg-primary text-white border border-primary rounded-md']" />
|
||||
</div>
|
||||
<ul id="filtersButtons" class="flex col-span-6 sm:col-span-4 md:col-span-4 lg:col-span-3 xl:col-span-2 justify-center">
|
||||
<li v-bind:key="idx" v-for="(mode, idx) in rightTopButtons">
|
||||
<Button :onClick="setFilteredAction" :selected="mode.key" :title="mode.name" :classIcon="mode.iconClass" :classBtn="[activeFilterBtn === mode.key ? 'focus:ring-4 focus:outline-none focus:ring-blue-300' : '', mode.key === 'filter' ? 'rounded-r-none' : 'rounded-l-none', mode.classBtn, 'w-full min-w-[88px] h-[30px] border border-primary rounded-md']" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="filters" v-if="toggleFilter">
|
||||
<Filters />
|
||||
</div>
|
||||
<div class="border-2 rounded-lg w-full bottom-0"></div>
|
||||
<div id="listMachines" v-if="selectedMode === 'cards'" class="w-full min-h-[80vh] p-4 bg-light dark:bg-slate-900 dark:text-slate-300 relative">
|
||||
<ul v-if="selectedMode === 'cards'" class="flex flex-wrap gap-4 w-full justify-center align-center">
|
||||
<!-- <li v-for="machine in machinesData" class="w-full sm:w-1/2 md:w-1/2 lg:w-1/2 xl:w-1/2 p-2 border-2 border-primary rounded-md"> -->
|
||||
<li v-for="machine in machinesData" class="">
|
||||
<Machine :machine="machine" :openMap="toggleMap" :openData="toggleData" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="tableMachines" v-if="selectedMode === 'table'" class="tab-pane bg-light relative py-4 text-center min-h-[300px] relative rounded-md w-full">
|
||||
<Table />
|
||||
</div>
|
||||
<div id="historyViews" v-if="selectedMode === 'history'" class="tab-pane bg-light relative shadow text-center min-h-[300px] relative w-full p-4">
|
||||
<HistoryViews />
|
||||
</div>
|
||||
<div id="charts" v-if="selectedMode === 'charts'" class="w-full min-h-[80vh] pb-5 bg-light dark:bg-slate-900 dark:text-slate-300 relative">
|
||||
<!-- <.loader :if={@display_config.is_loading_charts} /> -->
|
||||
<div>
|
||||
<Charts />
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div id="no_data" :if={@display_config.no_data && @display_config.machines_display_mode != "history_views"} class="w-full min-h-[80vh] bg-light dark:bg-slate-900 dark:text-slate-300 relative">
|
||||
<.loader :if={@list == [] && !@display_config.no_data || @display_config.is_loading_list} />
|
||||
<div class="flex items-center justify-center text-lg font-bold pt-5">Нет данных</div>
|
||||
</div> -->
|
||||
<MapModal
|
||||
:open="openMap"
|
||||
:close="closeMap"
|
||||
:data="mapData"
|
||||
id="mapModal"
|
||||
headerClass="border-b p-4 font-bold"
|
||||
>
|
||||
<template #header>
|
||||
<div id="headerTitle">
|
||||
Карта
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="!mapData" id="map_loader" class="flex justify-center" style="width: 95vw; height: 82vh;">
|
||||
<Spinner />
|
||||
</div>
|
||||
<div v-if="mapData" id="mapModal" style="width: 95vw; height: 82vh;"></div>
|
||||
<div id="popup" class="ol-popup hidden transition-all">
|
||||
<a href="#" id="popup-closer" class="ol-popup-closer hover:no-underline top-2 right-2 ">
|
||||
<i class="ri-close-fill"></i>
|
||||
</a>
|
||||
<div id="popup-content"></div>
|
||||
</div>
|
||||
</MapModal>
|
||||
<DataModal id="dataModal" :open="openData" :close="closeData" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<script>
|
||||
import {mapGetters, mapActions, mapMutations, useStore} from "vuex"
|
||||
import Tabulator from "@molecules/Tabulator/index.vue"
|
||||
import { EventStore } from "@store/hooks/EventStore"
|
||||
|
||||
export default {
|
||||
name: 'Table',
|
||||
components: {Tabulator},
|
||||
data () {
|
||||
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('machines', ['tableData', 'pagination']),
|
||||
tabulatorOtps() {
|
||||
return {
|
||||
dataSource: this.tableData,
|
||||
pagination: {...this.pagination, updatePagination: this.updatePagination},
|
||||
columns:
|
||||
[
|
||||
{
|
||||
title: "Тип и № машины",
|
||||
field: "machine_type",
|
||||
width: 150,
|
||||
log: true,
|
||||
render: `<div class="flex items-center gap-2">
|
||||
<a target="_blank" class="underline decoration-dashed" href="/html/askr_devices/analyze_machine/{{ machine_id }}">{{machine_type}}</i></a>
|
||||
</div>`
|
||||
},
|
||||
{
|
||||
title: "Imei",
|
||||
field: "imei",
|
||||
width: 150,
|
||||
log: true,
|
||||
render: `<div class="flex items-center gap-2">
|
||||
<a target="_blank" class="underline decoration-dashed" href="/html/askr_devices/analyze_comm/{{ imei }}">{{imei}}</i></a>
|
||||
</div>`
|
||||
},
|
||||
{
|
||||
title: "Дорога",
|
||||
field: "railway_name",
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: "Предприятие",
|
||||
field: "org_name",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "КР устройства",
|
||||
field: "device_number",
|
||||
width: 120,
|
||||
log: true,
|
||||
render: `<div class="flex items-center gap-2">
|
||||
<a target="_blank" class="underline decoration-dashed" href="/html/askr_devices/analyze_device/{{ device_number }}">{{device_number}}</i></a>
|
||||
</div>`
|
||||
},
|
||||
{
|
||||
title: "Номер",
|
||||
field: "nomer_zn8",
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: "Заводской номер",
|
||||
field: "zav_nomer",
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: "ID машины",
|
||||
field: "machine_id",
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: "ID устройства",
|
||||
field: "device_id",
|
||||
width: 90
|
||||
}
|
||||
],
|
||||
height: "550px"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('machines', ['setHistoryData']),
|
||||
...mapActions('machines', ['updatePagination']),
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tabulator v-bind="tabulatorOtps" />
|
||||
</template>
|
||||
89
live_monitor_vue/src/components/5_pages/Main/index.vue
Normal file
89
live_monitor_vue/src/components/5_pages/Main/index.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {computed} from 'vue'
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex";
|
||||
import {useRoute} from 'vue-router'
|
||||
import Machines from "./Machines/Machines.vue";
|
||||
import FinderPacks from "./FinderPacks/FinderPacks.vue";
|
||||
import AddUsers from "./AddUsers/AddUsers.vue";
|
||||
import Button from "@/components/1_atoms/Button.vue";
|
||||
|
||||
export default {
|
||||
name: 'Main',
|
||||
components: {
|
||||
Machines,
|
||||
FinderPacks,
|
||||
AddUsers,
|
||||
Button
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const path = computed(() => route.path)
|
||||
store.dispatch('main/uploadData', path.value)
|
||||
store.commit('main/setPath', path.value)
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
enabled: true,
|
||||
dragging: false,
|
||||
isTopScrolled: true,
|
||||
containerOffset: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('main', ['pages', 'activePath', 'selectedPage']),
|
||||
...mapGetters('layout', ['isOpenMenu']),
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
unmounted() {
|
||||
const store = useStore();
|
||||
store.dispatch('main/resetStore')
|
||||
},
|
||||
updated() {
|
||||
this.$nextTick(function () {
|
||||
const path = this.$route.path
|
||||
if (path !== this.activePath) {
|
||||
const path = this.$route.path
|
||||
this.$router.push({ path: path, query: {} })
|
||||
this.setPath(path)
|
||||
this.uploadData(path)
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('main', ['setPage', 'setPath']),
|
||||
...mapActions('main', ['uploadData']),
|
||||
setSelPage: function(page) {
|
||||
console.log('page', page)
|
||||
return this.setPage(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div class="col-span-12">
|
||||
<div class="hidden" id="ignore:21QxOfq">
|
||||
{{ console.log('selectedPage', selectedPage) }}
|
||||
</div>
|
||||
<div class="p-2 sm:px-5 pt-1 bg-light rounded-t-xl">
|
||||
<div class="relative">
|
||||
<div class="flex gap-3 sm:gap-7 w-full lg:w-[50%]">
|
||||
<router-link :to="{name: page.link}" @click="setPage(page)" v-for="(page, idx) in pages" class="flex w-full">
|
||||
<Button v-bind:key="idx" type="button" :title="page.name" :classBtn="[selectedPage.key !== page.key ? 'border-transparent': 'border-slate-500', 'flex border-b-4']" />
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="border-2 rounded-lg w-full bottom-0 absolute"/>
|
||||
</div>
|
||||
</div>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
20
live_monitor_vue/src/components/5_pages/News/index.vue
Normal file
20
live_monitor_vue/src/components/5_pages/News/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,65 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import Chart from "@/components/2_molecules/Chart/index.vue";
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
name: 'PackChart',
|
||||
components: {
|
||||
Chart,
|
||||
},
|
||||
props: {
|
||||
curerentTableData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
displayView: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentSizes: {
|
||||
width: document.documentElement.clientWidth - 700,
|
||||
height: document.documentElement.clientHeight - 350
|
||||
},
|
||||
maxWidth: document.documentElement.clientWidth - 300,
|
||||
maxHeight: document.documentElement.clientHeight - 300,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
chartData: function(params) {
|
||||
console.log('params', params)
|
||||
const yAxisAata = params.columns.map(column => {
|
||||
const value = params.dataSource.map((el) => {
|
||||
const dt = moment(el[column.pack_dt]).format('DD.MM.YYYY HH:mm')
|
||||
return {
|
||||
datetime: dt, param: column.key, state: el[column.key] ? el[column.key] : 0
|
||||
}
|
||||
})
|
||||
return {key: column.key, name: column.title, value}
|
||||
})
|
||||
console.log('yAxisAata', yAxisAata)
|
||||
const xAxisData = params.dataSource.map((el) => {
|
||||
const dt = moment(el.pack_dt).format('DD.MM.YYYY HH:mm')
|
||||
return dt
|
||||
})
|
||||
return {charts: yAxisAata, names: {yAxis: ' ', xAxis: 'Дата и время'}, xAxisData, title: 'График'}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
{{ console.log('currentSizes.heigth', maxHeight) }}
|
||||
<div v-if="displayView === 'chart'" id="packChartWrapper" class="relative flex flex-wrap justify-center w-full h-full mb-3" >
|
||||
<Chart id="packChart" type="packLines" height="800" :data="chartData(curerentTableData)" :sizes="currentSizes" :maxWidth="`${maxWidth}px`" :maxHeight="`${maxHeight}px`" />
|
||||
</div>
|
||||
</template>
|
||||
558
live_monitor_vue/src/components/5_pages/Packs/index.vue
Normal file
558
live_monitor_vue/src/components/5_pages/Packs/index.vue
Normal file
@@ -0,0 +1,558 @@
|
||||
<script>
|
||||
import {ref, toRaw, computed} from 'vue'
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex";
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import Datepicker from "@molecules/Datepicker/index.vue"
|
||||
import Spinner from "@molecules/Spinner/index.vue"
|
||||
import Tabulator from "@molecules/Tabulator/index.vue"
|
||||
import PackChart from './Chart/Chart.vue'
|
||||
import VueMultiselect from 'vue-multiselect'
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
name: 'Packs',
|
||||
components: {
|
||||
Datepicker,
|
||||
VueMultiselect,
|
||||
Tabulator,
|
||||
PackChart,
|
||||
Spinner
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
defaultDate: computed(() => this.setPickerFormat(this.packsSettings.dates.selectedDate)),
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const path = computed(() => route.path)
|
||||
const query = computed(() => route.query)
|
||||
|
||||
console.log('path', path)
|
||||
console.log('query', query)
|
||||
store.commit('packs/setIsLoading', true)
|
||||
store.dispatch('packs/uploadData', {path: path.value, query: query.value})
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columnSelector: false,
|
||||
displayView: 'table',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('packs', ['selectedPacksGroup', 'selectedPack', 'tableData', 'packsSettings', 'columnsSelect', 'selectedColumnsSelect', 'isLoading']),
|
||||
curerentTableData () {
|
||||
const pagination = {...this.packsSettings.pagination, updatePagination: this.updatePagination}
|
||||
return {
|
||||
...this.tableData, pagination
|
||||
}
|
||||
},
|
||||
},
|
||||
unmounted() {
|
||||
const store = useStore()
|
||||
store.dispatch('packs/resetStore')
|
||||
},
|
||||
methods: {
|
||||
...mapActions('packs', ['uploadData', 'updatePagination', 'updatePackSettings', 'updateSelects']),
|
||||
...mapMutations('packs', ['setSelectedPack']),
|
||||
updateSelectedPack: function(value) {
|
||||
this.setSelectedPack(value)
|
||||
},
|
||||
setPackSettings: function(value, key) {
|
||||
console.log('value', value)
|
||||
console.log('key', key)
|
||||
switch (key) {
|
||||
case 'date':
|
||||
this.updatePackSettings({dates: {...this.packsSettings.dates, selectedDate: value}})
|
||||
break
|
||||
case 'imei':
|
||||
this.updatePackSettings({imei: value})
|
||||
break
|
||||
case 'selectedPack':
|
||||
this.updatePackSettings({selectedPack: value})
|
||||
break
|
||||
}
|
||||
},
|
||||
setDate: function(value) {
|
||||
console.log('value', value)
|
||||
this.updatePackSettings({dates: {...this.packsSettings.dates, selectedDate: value}})
|
||||
},
|
||||
setImei: function(e) {
|
||||
console.log('value', e.target.value)
|
||||
this.updatePackSettings({imei: e.target.value})
|
||||
},
|
||||
setPack: function(value) {
|
||||
console.log('value', value)
|
||||
this.updatePackSettings({selectedPack: value})
|
||||
},
|
||||
fetchPack: function(params) {
|
||||
console.log('params', params)
|
||||
if (params.imei && params.dates.selectedDate && params.selectedPack) {
|
||||
const path = this.$route.path
|
||||
const query = this.$route.query
|
||||
const dateUrl = moment(params.dates.selectedDate).format('YYYY-MM-DD')
|
||||
console.log('params.selectedPack', params.selectedPack)
|
||||
const queryUrl = {...query, date: dateUrl, imei: params.imei, pack: params.selectedPack.name}
|
||||
this.$router.push({ path: path, query: queryUrl})
|
||||
const currentQuery = {...queryUrl, date: params.dates.selectedDate}
|
||||
this.uploadData({path: path, query: currentQuery})
|
||||
}
|
||||
},
|
||||
setPickerFormat: (date) => {
|
||||
console.log('moment(date)', moment(date).format('DD.MM.YYYY'))
|
||||
if (!date) return null
|
||||
return moment(date).format('DD.MM.YYYY')
|
||||
},
|
||||
setOpenColumnSelect: function() {
|
||||
return this.columnSelector = !this.columnSelector
|
||||
},
|
||||
toggleDisplayView: function() {
|
||||
this.displayView = this.displayView === 'chart' ? 'table' : 'chart'
|
||||
},
|
||||
addedOption: function(value) {
|
||||
console.log('value', value)
|
||||
this.updateSelects({key: 'added', select: value})
|
||||
},
|
||||
removedOption: function(value) {
|
||||
this.updateSelects({key: 'removed', select: value})
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-12 gap-6 col-span-12 h-full">
|
||||
{{
|
||||
// console.log('tableData', tableData)
|
||||
console.log('packsSettings', packsSettings)
|
||||
}}
|
||||
<div class="rounded-md bg-light px-4 pt-4 pb-6 gap-2 shadow lg:col-span-4 xl:col-span-3 2xl:col-span-2 col-span-12">
|
||||
<h1 class="font-medium leading-tight text-xl mb-4 text-slate-600">
|
||||
Первый пакет
|
||||
</h1>
|
||||
<div class="grid gap-3 mb-3 ">
|
||||
<div class="grid grid-cols-1">
|
||||
<label for="date" class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300">
|
||||
Дата
|
||||
</label>
|
||||
<Datepicker id="date" :onchange="setDate" />
|
||||
</div>
|
||||
<div phx-feedback-for="imei">
|
||||
<label for="selectedImei" class="block text-sm leading-6 text-zinc-800 dark:text-white">
|
||||
imei
|
||||
</label>
|
||||
<input
|
||||
id="selectedImei"
|
||||
type="text"
|
||||
name="imei"
|
||||
v-bind:value="packsSettings.imei"
|
||||
v-on:change="setImei"
|
||||
class="block w-full rounded-lg border-zinc-300 py-[1px] px-[11px] text-zinc-900 focus:outline-none focus:ring-4 text-sm sm:leading-6 phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5 border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5 "
|
||||
placeholder="imei"
|
||||
required=""
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label for="selectedPack" class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400"
|
||||
>Выбрать пакет</label>
|
||||
<div id="selected_settings_name_wrapper" class="w-full">
|
||||
<div id="packsOptions" class="col-span-12 sm:col-span-6 lg:col-span-3 rounded-lg">
|
||||
<VueMultiselect
|
||||
name="selectedPack"
|
||||
v-model="packsSettings.selectedPack"
|
||||
label="name"
|
||||
track-by="name"
|
||||
:options="packsSettings.selectedPackGroup"
|
||||
:placeholder="packsSettings.selectedPack?.name"
|
||||
@update:model-value="setPack"
|
||||
:showLabels="false"
|
||||
>
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-3 grid-cols-4">
|
||||
<input
|
||||
type="submit"
|
||||
@click="fetchPack(packsSettings)"
|
||||
class="col-span-4 lg:col-span-4 cursor-pointer text-white bg-primary hover:brightness-90 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-ssm w-full sm:w-auto px-5 py-1 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 transition_up"
|
||||
value="Выгрузить"
|
||||
>
|
||||
<div
|
||||
phx-click="load_pack_settings"
|
||||
class="cursor-pointer col-span-4 xl:col-span-4 bg-transparent hover:bg-primary text-primary font-semibold hover:text-white py-1 text-ssm px-4 border border-primary hover:border-transparent rounded-lg text-center transition_up"
|
||||
>
|
||||
Настройки
|
||||
</div>
|
||||
<div
|
||||
class="cursor-pointer col-span-4 lg:col-span-4 bg-transparent hover:bg-primary text-primary font-semibold hover:text-white py-1 text-ssm px-4 border border-primary hover:border-transparent rounded-lg text-center transition_up"
|
||||
phx-click="[["show",{"display":null,"time":200,"to":"#select_machines_modal","transition":[[],[],[]]}],["show",{"display":null,"time":200,"to":"#machines_modal_loader","transition":[[],[],[]]}],["push",{"event":"load_machines_table","value":{"mode":"page"}}]]"
|
||||
>
|
||||
Выбрать машину
|
||||
</div>
|
||||
<div
|
||||
id="question"
|
||||
class="cursor-pointer col-span-4 xl:col-span-4 bg-transparent hover:bg-primary text-primary font-semibold hover:text-white py-1 text-ssm px-4 border border-primary hover:border-transparent rounded-lg text-center transition_up"
|
||||
phx-hook="InfoModal"
|
||||
>
|
||||
<i class="ri-question-mark" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="packageDisplay" class="relative lg:col-span-8 xl:col-span-9 2xl:col-span-10 col-span-12 bg-light shadow">
|
||||
<div v-if="!isLoading && curerentTableData.dataSource" id="tableButtons" class="w-full">
|
||||
<div class="w-full flex justify-end">
|
||||
<div class="flex gap-3 pt-4 pb-2 pr-4">
|
||||
<div class="">
|
||||
<button
|
||||
type="button"
|
||||
@click="setOpenColumnSelect"
|
||||
class="flex items-center transition-all justify-center py-2 px-3 bg-light text-sm font-medium text-slate-700 focus:outline-none rounded-lg border border-dashed border-slate-400 hover:text-blue-800 hover:border-blue-900 hover:shadow focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
||||
>
|
||||
<i class="ri-settings-3-line" />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
id="graphic_btn_inactive"
|
||||
class=""
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
@click="toggleDisplayView"
|
||||
class="flex items-center transition-all justify-center py-2 px-3 bg-light text-sm font-medium text-slate-700 focus:outline-none rounded-lg border border-dashed border-slate-400 hover:text-blue-800 hover:border-blue-900 hover:shadow focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
||||
phx-click="[["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#graphic_btn_inactive"}],["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#graphic_btn_active"}],["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#pack_table_warapper"}],["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#pack_graphic_wrapper"}],["push",{"event":"toggle_display_data"}]]"
|
||||
>
|
||||
<i class="ri-bar-chart-grouped-line" />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
id="graphic_btn_active"
|
||||
class="hidden"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center transition-all justify-center py-2 px-3 bg-blue-200 text-sm font-medium text-blue-700 focus:outline-none rounded-lg border border-dashed border-blue-500 hover:text-blue-800 hover:border-blue-900 hover:shadow focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
||||
phx-click="[["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#graphic_btn_inactive"}],["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#graphic_btn_active"}],["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#pack_table_warapper"}],["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#pack_graphic_wrapper"}],["push",{"event":"toggle_display_data"}]]"
|
||||
>
|
||||
<i class="ri-bar-chart-grouped-line" />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
id="excel_btn"
|
||||
phx-click="download_csv"
|
||||
style=""
|
||||
class="flex items-center justify-center transition-all justify-center py-2 px-3 bg-light text-sm font-medium text-slate-700 focus:outline-none rounded-lg border border-dashed border-slate-400 hover:text-blue-800 hover:border-blue-900 hover:shadow focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 cursor-pointer"
|
||||
>
|
||||
<i class="ri-file-excel-line" />
|
||||
</div>
|
||||
<div
|
||||
id="excel_loader"
|
||||
style="display: none"
|
||||
class="flex items-center justify-center transition-all justify-center py-2 px-3 bg-light text-sm font-medium text-slate-700 focus:outline-none rounded-lg border border-dashed border-slate-400 hover:text-blue-800 hover:border-blue-900 hover:shadow focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 "
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="w-4 h-4 text-gray-300 animate-spin dark:text-gray-400 fill-slate-700"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="columnSelector" id="colums_select_wrapper" class="w-full">
|
||||
<VueMultiselect
|
||||
name="selectedPack"
|
||||
v-model="selectedColumnsSelect"
|
||||
label="name"
|
||||
track-by="name"
|
||||
:options="columnsSelect"
|
||||
:placeholder="null"
|
||||
@select="addedOption"
|
||||
@remove="removedOption"
|
||||
:showLabels="false"
|
||||
:multiple="true"
|
||||
:group-select="true"
|
||||
>
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
<div v-if="displayView === 'table'" id="packTableWarapper">
|
||||
{{ console.log('getters.isLoading', isLoading) }}
|
||||
<div id="reportPacks" class="tab-pane active text-center min-h-[300px] rounded-md ">
|
||||
<Tabulator v-if="!isLoading && curerentTableData.dataSource" v-bind="curerentTableData" />
|
||||
<span v-if="!isLoading && !curerentTableData.dataSource" class="absolute flex w-full justify-center items-center mt-2"> Выберите пакет и нажмите выгрузить</span>
|
||||
<div v-if="isLoading && !curerentTableData.dataSource" class="absolute flex w-full h-full justify-center items-center bg-slate-50" id="packLoader">
|
||||
<Spinner />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PackChart :displayView="displayView" :curerentTableData="curerentTableData" />
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="hidden"
|
||||
phx-click="[["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#period_table_modal"}]]"
|
||||
/>
|
||||
<div
|
||||
id="period_table_modal"
|
||||
class="fixed z-[10000] inset-0 hidden"
|
||||
phx-remove="[["transition",{"time":200,"to":null,"transition":[["fade-out"],[],[]]}]]"
|
||||
>
|
||||
<!-- Modal container -->
|
||||
<div class="h-screen flex items-center justify-center p-4">
|
||||
<!-- Overlay -->
|
||||
<div
|
||||
class="absolute z-0 inset-0 bg-gray-500 opacity-75"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<!-- Modal box -->
|
||||
<div
|
||||
class="relative max-h-full overflow-y-auto bg-white rounded-lg shadow-xl "
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="0"
|
||||
autofocus=""
|
||||
phx-window-keydown="[["hide",{"time":200,"to":"#period_table_modal","transition":[["ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["dispatch",{"event":"click","to":"#period_table_modal-return"}]]"
|
||||
phx-key="escape"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-2 right-2 text-gray-400 flex space-x-1 items-center"
|
||||
aria_label="close modal"
|
||||
phx-click="[["hide",{"time":200,"to":"#period_table_modal","transition":[["ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["dispatch",{"event":"click","to":"#period_table_modal-return"}]]"
|
||||
>
|
||||
<span class="text-sm">(esc)</span>
|
||||
<i class="ri ri-close-line text-2xl" />
|
||||
</button>
|
||||
|
||||
<div class="min-w-[350px] min-h-[400px] py">
|
||||
<div class=" p-4 font-bold" />
|
||||
|
||||
<div
|
||||
id="pack-settings-editor"
|
||||
phx-hook="PackSettingsEditor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="ignore:31QzedwP"
|
||||
phx-update="ignore"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="hidden"
|
||||
phx-click="[["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#select_machines_modal"}]]"
|
||||
/>
|
||||
<div
|
||||
id="select_machines_modal"
|
||||
class="fixed z-[10000] inset-0 hidden"
|
||||
phx-remove="[["transition",{"time":200,"to":null,"transition":[["fade-out"],[],[]]}]]"
|
||||
>
|
||||
<!-- Modal container -->
|
||||
<div class="h-screen flex items-center justify-center p-4">
|
||||
<!-- Overlay -->
|
||||
<div
|
||||
class="absolute z-0 inset-0 bg-gray-500 opacity-75"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<!-- Modal box -->
|
||||
<div
|
||||
class="relative max-h-full overflow-y-auto bg-white rounded-lg shadow-xl "
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="0"
|
||||
autofocus=""
|
||||
phx-window-keydown="[["hide",{"time":200,"to":"#select_machines_modal","transition":[["ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["dispatch",{"event":"click","to":"#select_machines_modal-return"}]]"
|
||||
phx-key="escape"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-2 right-2 text-gray-400 flex space-x-1 items-center"
|
||||
aria_label="close modal"
|
||||
phx-click="[["hide",{"time":200,"to":"#select_machines_modal","transition":[["ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["dispatch",{"event":"click","to":"#select_machines_modal-return"}]]"
|
||||
>
|
||||
<span class="text-sm">(esc)</span>
|
||||
<i class="ri ri-close-line text-2xl" />
|
||||
</button>
|
||||
|
||||
<div class="min-w-[350px] min-h-[400px] py">
|
||||
<div class=" p-4 font-bold" />
|
||||
|
||||
<div class="w-[95vw] h-[95vh] flex flex-col gap-8 p-5">
|
||||
<div id="machines_modal_loader">
|
||||
<div
|
||||
style=""
|
||||
class="h-full absolute inset-0 flex flex-row items-center grow transition items-center justify-center appear_from_opacity-3 z-30 rounded p-2"
|
||||
>
|
||||
<div class="flex flex-row items-center grow transition items-center justify-center rounded shadow bg-white h-full">
|
||||
<div
|
||||
type-node="loader"
|
||||
class="flex gap-3 items-center justify-center "
|
||||
>
|
||||
<span class="text-slate-600 text-md font-medium">Загрузка</span>
|
||||
<div role="status">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mr-2 w-6 h-6 text-gray-300 animate-spin dark:text-gray-400 fill-slate-700"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="select_machines"
|
||||
no-init="true"
|
||||
phx-hook="MakeTabulator"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="hidden"
|
||||
phx-click="[["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#map_modal"}]]"
|
||||
/>
|
||||
<div
|
||||
id="map_modal"
|
||||
class="fixed z-[10000] inset-0 hidden"
|
||||
phx-remove="[["transition",{"time":200,"to":null,"transition":[["fade-out"],[],[]]}]]"
|
||||
>
|
||||
<!-- Modal container -->
|
||||
<div class="h-screen flex items-center justify-center p-4">
|
||||
<!-- Overlay -->
|
||||
<div
|
||||
class="absolute z-0 inset-0 bg-gray-500 opacity-75"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<!-- Modal box -->
|
||||
<div
|
||||
class="relative max-h-full overflow-y-auto bg-white rounded-lg shadow-xl "
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="0"
|
||||
autofocus=""
|
||||
phx-window-keydown="[["hide",{"time":200,"to":"#map_modal","transition":[["ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["dispatch",{"event":"click","to":"#map_modal-return"}]]"
|
||||
phx-click-away="[["hide",{"time":200,"to":"#map_modal","transition":[["ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["dispatch",{"event":"click","to":"#map_modal-return"}]]"
|
||||
phx-key="escape"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-2 right-2 text-gray-400 flex space-x-1 items-center"
|
||||
aria_label="close modal"
|
||||
phx-click="[["hide",{"time":200,"to":"#map_modal","transition":[["ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["dispatch",{"event":"click","to":"#map_modal-return"}]]"
|
||||
>
|
||||
<span class="text-sm">(esc)</span>
|
||||
<i class="ri ri-close-line text-2xl" />
|
||||
</button>
|
||||
|
||||
<div class="min-w-[350px] min-h-[400px] py">
|
||||
<div class="border-b p-4 font-bold">
|
||||
Карта
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="ignore:7"
|
||||
phx-update="ignore"
|
||||
class="w-[95vw] h-[85vh] m-auto relative"
|
||||
>
|
||||
<div id="map_loader">
|
||||
<div
|
||||
style="padding: 0"
|
||||
class="h-full absolute inset-0 flex flex-row items-center grow transition items-center justify-center appear_from_opacity-3 z-30 rounded p-2"
|
||||
>
|
||||
<div class="flex flex-row items-center grow transition items-center justify-center rounded shadow bg-white h-full">
|
||||
<div
|
||||
type-node="loader"
|
||||
class="flex gap-3 items-center justify-center "
|
||||
>
|
||||
<span class="text-slate-600 text-md font-medium">Загрузка</span>
|
||||
<div role="status">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mr-2 w-6 h-6 text-gray-300 animate-spin dark:text-gray-400 fill-slate-700"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="map_show"
|
||||
phx-hook="CoordsMap"
|
||||
style="width: 95vw; height: 82vh;"
|
||||
/>
|
||||
<div
|
||||
id="popup"
|
||||
class="ol-popup hidden transition-all"
|
||||
>
|
||||
<a
|
||||
id="popup-closer"
|
||||
href="#"
|
||||
class="ol-popup-closer hover:no-underline top-2 right-2 "
|
||||
>
|
||||
<i class="ri-close-fill" />
|
||||
</a>
|
||||
<div id="popup-content" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
20
live_monitor_vue/src/components/6_admin_pages/Diap/index.vue
Normal file
20
live_monitor_vue/src/components/6_admin_pages/Diap/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
20
live_monitor_vue/src/components/6_admin_pages/News/index.vue
Normal file
20
live_monitor_vue/src/components/6_admin_pages/News/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,105 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {mapActions, useStore} from "vuex"
|
||||
import Spinner from "@molecules/Spinner/index.vue"
|
||||
import { CopyToClipboard } from "@store/hooks/CopyToClipboard"
|
||||
|
||||
export default {
|
||||
name: 'Service',
|
||||
components: {
|
||||
Spinner,
|
||||
},
|
||||
props: {
|
||||
idx: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
service: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
copyed: {
|
||||
type: String,
|
||||
default: "copyed"
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isCopyed: false
|
||||
}
|
||||
},
|
||||
setup (props, {slots}) {
|
||||
const store = useStore()
|
||||
store.dispatch('logger/uploadData', {status: 'info', text: 'Сервис Info'})
|
||||
},
|
||||
methods: {
|
||||
...mapActions('services', ['uploadServiceData']),
|
||||
isInitParams: function(service) {
|
||||
if (service?.init_params && typeof service?.init_params === 'object') {
|
||||
if (service?.init_params?.service && service?.init_params?.machine_addr) return true
|
||||
}
|
||||
},
|
||||
copyText: function (e) {
|
||||
const id = e.target.getAttribute('id')
|
||||
const text = e.target.getAttribute('copyOnClickText')
|
||||
this.isCopyed = true
|
||||
return new CopyToClipboard('serviceList', text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div :key="idx" style="min-width: 240px" class="h-full overflow-hidden shadow-lg rounded-lg h-auto w-80 my-2 md:w-80 p-4 bg-indigo-50 transition duration-200 ease-in-out transform hover:translate-y-1 hover:shadow-2xl">
|
||||
<div class="w-full flex flex-col h-full justify-center">
|
||||
<div class="flex my-2 justify-between">
|
||||
<span class="font-bold"> {{ service?.title }} </span>
|
||||
<span v-if="service?.is_online || name === 'глобальное хранилище'" class="font-bold text-green-700">доступен</span>
|
||||
<span v-else class="font-bold text-red-800">отключен </span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="text-gray-700 my-3 flex items-center flex-grow">
|
||||
<div v-if="service?.serviceIp || service?.serviceCode" class="flex flex-col gap-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="italic text-xs font-bold">Детали</span>
|
||||
<span v-if="service?.serviceIp" class="italic text-xs">{{ service?.serviceIp }}</span>
|
||||
<span v-if="service?.serviceCode" class="italic text-xs">{{ service?.serviceCode }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="italic text-xs font-bold">Последний перезапуск(UTC):</span>
|
||||
<span class="italic text-xs">{{ service?.dateStart }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="italic text-xs font-bold">Описание сервиса:</span>
|
||||
<span class="italic text-xs">{{ service?.description || "... нет описания" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-col">
|
||||
<span class="italic text-xs font-bold">Описание сервиса:</span>
|
||||
<span class="italic text-xs">Сервис глобального хранилища применяется для условно постоянного хранения данных в кэше</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="mt-2">
|
||||
<span v-if="isInitParams(service)" class="flex justify-between" >
|
||||
<span @click="copyText" :copyOnClickText="`${service?.init_params?.service}:${service?.init_params?.machine_addr}`" :copyed="`${isCopyed ? copyed : ''}`" class="copyText italic text-xs flex cursor-pointer" :id="`service_${service?.init_params?.service}`">
|
||||
<i class="ri-file-copy-2-line text-slate-600 mr-1"></i>
|
||||
{{ service?.serviceLink }}
|
||||
</span>
|
||||
<a :href="`/admin_panel/auth_service?show_service_cache=${name}`" id="selectService" @click="uploadServiceData(service)" class="flex italic text-xs cursor-pointer underline hover:text-gray-500 linear-loader">
|
||||
открыть
|
||||
</a>
|
||||
</span>
|
||||
<div v-else class="italic text-xs">нет доступа к глоб. хранилищу</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,74 @@
|
||||
import Services from './index.vue'
|
||||
import { globalServices } from '@store/modules/services/StaticData'
|
||||
import { shallowMount, mount } from '@vue/test-utils'
|
||||
import {createStore} from 'vuex'
|
||||
|
||||
const store = createStore({
|
||||
state() {
|
||||
return {
|
||||
services: globalServices,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
services: (state) => state.services,
|
||||
},
|
||||
mutations: {
|
||||
setServices: (state, updval) => state.services = updval,
|
||||
},
|
||||
})
|
||||
|
||||
describe("Services", () => {
|
||||
it("renders Services, with custom button title", () => {
|
||||
const wrapper = mount(Services, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
titleServicesButton: 'Показать больше сервисов',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('Показать больше сервисов')
|
||||
})
|
||||
})
|
||||
|
||||
describe("Services", () => {
|
||||
it("renders after click button, open full Services", async () => {
|
||||
const wrapper = mount(Services, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.find('button').trigger('click')
|
||||
expect(wrapper.find('button').text()).toContain('Скрыть')
|
||||
})
|
||||
})
|
||||
|
||||
describe("Services", () => {
|
||||
it("renders Services with exists data", async () => {
|
||||
const wrapper = mount(Services, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
},
|
||||
})
|
||||
|
||||
expect(store.state.services.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Services", () => {
|
||||
it("renders Services maked empty data", async () => {
|
||||
const wrapper = mount(Services, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
},
|
||||
})
|
||||
|
||||
store.commit('setServices', [])
|
||||
|
||||
expect(store.state.services.length).toBe(0)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,67 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {mapGetters, useStore} from "vuex"
|
||||
import Service from './Service.vue'
|
||||
import Spinner from "@molecules/Spinner/index.vue"
|
||||
import Tabulator from "@molecules/Tabulator/index.vue"
|
||||
|
||||
export default {
|
||||
name: 'Services',
|
||||
components: {
|
||||
Service,
|
||||
Spinner,
|
||||
Tabulator,
|
||||
},
|
||||
props: {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isOpenServices: false,
|
||||
titleServicesButton: 'Показать больше'
|
||||
}
|
||||
},
|
||||
setup (props, {slots}) {
|
||||
const store = useStore()
|
||||
store.dispatch('services/uploadData')
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('services', ['services']),
|
||||
},
|
||||
methods: {
|
||||
sortingServices: function (services) {
|
||||
return services?.sort((a, b) => b.is_online - a.is_online)
|
||||
},
|
||||
toggleServices: function (isOpenServices) {
|
||||
this.isOpenServices = !isOpenServices
|
||||
this.titleServicesButton = this.isOpenServices ? 'Скрыть' : 'Показать больше'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div class="col-span-12">
|
||||
<div class="flex flex-col mt-4 shadow-md mb-1 mx-5 bg-white" style="max-width: calc(100vw - 40px); margin-bottom: 30px;">
|
||||
<div class="flex flex-col flex-wrap justify-between px-4 pb-2 pt-8 w-full">
|
||||
<div class="text-lg font-bold text-center text-gray-600">Информация о сети (кэш)</div>
|
||||
<div class="flex flex-col max-w-full">
|
||||
<div>
|
||||
Сервисы, записывающие данные в кэш
|
||||
</div>
|
||||
<div id="serviceList" :class="`flex flex-wrap h-auto flex-row mt-4 overflow-x-auto w-full gap-4 transition-all pb-4 ${isOpenServices ? 'max-h-full' : 'max-h-[220px]'}`">
|
||||
<div v-for="(service, idx) in sortingServices(services)">
|
||||
<Service :idx="idx" :name="service.service || service.name" :service="service" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="toggleServices(isOpenServices)" class="w-full max-h-[90px] py-2.5 px-5 text-sm font-medium text-gray-900 bg-gray-100 border-t border-gray-200 hover:bg-gray-100 hover:text-slate-700 dark:bg-gray-700 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">
|
||||
{{titleServicesButton}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,122 @@
|
||||
import Service from './Service.vue'
|
||||
import { globalServices } from '@store/modules/services/StaticData'
|
||||
import { shallowMount, mount } from '@vue/test-utils'
|
||||
import {createStore} from 'vuex'
|
||||
|
||||
const store = createStore({
|
||||
state() {
|
||||
return {
|
||||
services: globalServices,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
services: (state) => state.services,
|
||||
},
|
||||
mutations: {
|
||||
setServices: (state, updval) => state.services = updval,
|
||||
},
|
||||
})
|
||||
|
||||
describe("Service", () => {
|
||||
it("renders Service, with set props value", async () => {
|
||||
const wrapper = mount(Service, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
},
|
||||
props: {
|
||||
idx: 1,
|
||||
},
|
||||
})
|
||||
await wrapper.setProps({ idx: 3 })
|
||||
|
||||
expect(wrapper.html()).toContain(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Service", () => {
|
||||
it("renders Service with condition success", async () => {
|
||||
const wrapper = mount(Service, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
},
|
||||
props: {
|
||||
name: '',
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.setProps({ name: 'глобальное хранилище' })
|
||||
|
||||
expect(wrapper.html()).toContain('доступен')
|
||||
})
|
||||
})
|
||||
|
||||
describe("Service", () => {
|
||||
it("renders Service with condition not success", async () => {
|
||||
const wrapper = mount(Service, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
},
|
||||
props: {
|
||||
name: '',
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.setProps({ name: 'Test service name' })
|
||||
|
||||
expect(wrapper.html()).toContain('отключен')
|
||||
})
|
||||
})
|
||||
|
||||
describe("Service", () => {
|
||||
it("renders Service if not service details", async () => {
|
||||
const wrapper = mount(Service, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
},
|
||||
props: {
|
||||
service: {},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.html()).toContain('Сервис глобального хранилища применяется для условно постоянного хранения данных в кэше')
|
||||
})
|
||||
})
|
||||
|
||||
describe("Service", () => {
|
||||
it("renders Service with set service details", async () => {
|
||||
const wrapper = mount(Service, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
},
|
||||
props: {
|
||||
service: {serviceIp: '192.168.1.1', serviceCode: '1234567890', serviceLink: 'http://test.com', is_online: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.setProps({ service: {serviceIp: '192.168.1.7', serviceCode: '1234567897', is_online: true} }) // 1 раз, далее новый тест
|
||||
|
||||
expect(wrapper.html()).toContain('192.168.1.7')
|
||||
expect(wrapper.html()).toContain('1234567897')
|
||||
expect(wrapper.html()).toContain('доступен')
|
||||
expect(wrapper.html()).toContain('... нет описания')
|
||||
})
|
||||
})
|
||||
|
||||
describe("Service", () => {
|
||||
it("renders Service with alert copyText success after click", async () => {
|
||||
const wrapper = mount(Service, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
},
|
||||
props: {
|
||||
copyed: '',
|
||||
service: {init_params: {service: 'Test service', machine_addr: 'Test machine_addr'}},
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.get('.copyText').trigger('click')
|
||||
|
||||
expect(wrapper.html()).toContain('copyed')
|
||||
})
|
||||
})
|
||||
761
live_monitor_vue/src/helpers/dom.js
Normal file
761
live_monitor_vue/src/helpers/dom.js
Normal file
@@ -0,0 +1,761 @@
|
||||
export default dom = (() => {
|
||||
const getNodeList = (arg) => {
|
||||
if (typeof arg === "string" && arg.trim().slice(0, 1) !== "<") {
|
||||
return document.querySelectorAll(arg);
|
||||
} else if (typeof arg === "string" && arg.trim().slice(0, 1) === "<") {
|
||||
const dom = domParser(arg);
|
||||
return [dom];
|
||||
} else if (typeof arg === "object" && arg instanceof NodeList) {
|
||||
return arg;
|
||||
} else if (typeof arg === "object" && arg instanceof HTMLElement) {
|
||||
return [arg];
|
||||
} else if (typeof arg === "object" && arg instanceof SVGElement) {
|
||||
return [arg];
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
};
|
||||
|
||||
const domParser = (arg) => {
|
||||
const parser = new DOMParser(),
|
||||
content = "text/html",
|
||||
DOM = parser.parseFromString(arg, content);
|
||||
return DOM.body.childNodes[0];
|
||||
};
|
||||
|
||||
const addEvent = (nodeList, event, callback) => {
|
||||
nodeList[event] = callback;
|
||||
};
|
||||
|
||||
const uuidv4 = () => {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||
let r = (Math.random() * 16) | 0,
|
||||
v = c == "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
|
||||
const eventPath = (evt) => {
|
||||
let path = (evt.composedPath && evt.composedPath()) || evt.path,
|
||||
target = evt.target;
|
||||
|
||||
if (path != null) {
|
||||
// Safari doesn't include Window, but it should.
|
||||
return path.indexOf(window) < 0 ? path.concat(window) : path;
|
||||
}
|
||||
|
||||
if (target === window) {
|
||||
return [window];
|
||||
}
|
||||
|
||||
function getParents(node, memo) {
|
||||
memo = memo || [];
|
||||
let parentNode = node.parentNode;
|
||||
|
||||
if (!parentNode) {
|
||||
return memo;
|
||||
} else {
|
||||
return getParents(parentNode, memo.concat(parentNode));
|
||||
}
|
||||
}
|
||||
|
||||
return [target].concat(getParents(target), window);
|
||||
};
|
||||
|
||||
const addEvents = (nodeList) => {
|
||||
// .on()
|
||||
addEvent(nodeList, "on", (arg1, arg2, arg3) => {
|
||||
nodeList.forEach((node) => {
|
||||
node.addEventListener(
|
||||
arg1,
|
||||
(e) => {
|
||||
const uuid = uuidv4();
|
||||
if (typeof arg2 === "string") {
|
||||
eventPath(e).every((parentNode) => {
|
||||
if (parentNode.matches && parentNode.matches(arg2)) {
|
||||
parentNode[uuid] = arg3;
|
||||
parentNode[uuid](parentNode);
|
||||
delete parentNode[uuid];
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
node[uuid] = arg2;
|
||||
node[uuid](e);
|
||||
delete node[uuid];
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .css()
|
||||
addEvent(nodeList, "css", (arg1, arg2) => {
|
||||
// Get values
|
||||
if (arg2 === undefined && typeof arg1 !== "object") {
|
||||
return getComputedStyle(nodeList[0])[arg1];
|
||||
}
|
||||
|
||||
// Set values
|
||||
nodeList.forEach((node) => {
|
||||
if (typeof arg1 === "object") {
|
||||
for (const [key, val] of Object.entries(arg1)) {
|
||||
node.style[key] = val;
|
||||
}
|
||||
} else {
|
||||
node.style[arg1] = arg2;
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .slideUp()
|
||||
addEvent(nodeList, "slideUp", (arg1 = 300, arg2 = () => {}) => {
|
||||
nodeList.forEach((node) => {
|
||||
node.style.transitionProperty = "height, margin, padding";
|
||||
node.style.transitionDuration = arg1 + "ms";
|
||||
node.style.height = node.offsetHeight + "px";
|
||||
node.offsetHeight;
|
||||
node.style.overflow = "hidden";
|
||||
node.style.height = 0;
|
||||
node.style.paddingTop = 0;
|
||||
node.style.paddingBottom = 0;
|
||||
node.style.marginTop = 0;
|
||||
node.style.marginBottom = 0;
|
||||
window.setTimeout(() => {
|
||||
node.style.display = "none";
|
||||
node.style.removeProperty("height");
|
||||
node.style.removeProperty("padding-top");
|
||||
node.style.removeProperty("padding-bottom");
|
||||
node.style.removeProperty("margin-top");
|
||||
node.style.removeProperty("margin-bottom");
|
||||
node.style.removeProperty("overflow");
|
||||
node.style.removeProperty("transition-duration");
|
||||
node.style.removeProperty("transition-property");
|
||||
|
||||
const uuid = uuidv4();
|
||||
node[uuid] = arg2;
|
||||
node[uuid](node);
|
||||
delete node[uuid];
|
||||
}, arg1);
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .slideDown()
|
||||
addEvent(nodeList, "slideDown", (arg1 = 300, arg2 = () => {}) => {
|
||||
nodeList.forEach((node) => {
|
||||
node.style.removeProperty("display");
|
||||
let display = window.getComputedStyle(node).display;
|
||||
if (display === "none") display = "block";
|
||||
node.style.display = display;
|
||||
let height = node.offsetHeight;
|
||||
node.style.overflow = "hidden";
|
||||
node.style.height = 0;
|
||||
node.style.paddingTop = 0;
|
||||
node.style.paddingBottom = 0;
|
||||
node.style.marginTop = 0;
|
||||
node.style.marginBottom = 0;
|
||||
node.offsetHeight;
|
||||
node.style.transitionProperty = "height, margin, padding";
|
||||
node.style.transitionDuration = arg1 + "ms";
|
||||
node.style.height = height + "px";
|
||||
node.style.removeProperty("padding-top");
|
||||
node.style.removeProperty("padding-bottom");
|
||||
node.style.removeProperty("margin-top");
|
||||
node.style.removeProperty("margin-bottom");
|
||||
window.setTimeout(() => {
|
||||
node.style.removeProperty("height");
|
||||
node.style.removeProperty("overflow");
|
||||
node.style.removeProperty("transition-duration");
|
||||
node.style.removeProperty("transition-property");
|
||||
|
||||
const uuid = uuidv4();
|
||||
node[uuid] = arg2;
|
||||
node[uuid](node);
|
||||
delete node[uuid];
|
||||
}, arg1);
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .fadeOut()
|
||||
addEvent(nodeList, "fadeOut", (arg1 = 300, arg2 = () => {}) => {
|
||||
nodeList.forEach((node) => {
|
||||
node.style.opacity = 1;
|
||||
node.style.transitionProperty = "opacity";
|
||||
node.style.transitionDuration = arg1 + "ms";
|
||||
node.style.opacity = 0;
|
||||
window.setTimeout(() => {
|
||||
node.style.display = "none";
|
||||
node.style.removeProperty("transition-property");
|
||||
node.style.removeProperty("transition-duration");
|
||||
node.style.removeProperty("opacity");
|
||||
|
||||
const uuid = uuidv4();
|
||||
node[uuid] = arg2;
|
||||
node[uuid](node);
|
||||
delete node[uuid];
|
||||
}, arg1);
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .fadeIn()
|
||||
addEvent(nodeList, "fadeIn", (arg1 = 300, arg2 = () => {}) => {
|
||||
nodeList.forEach((node) => {
|
||||
let display = window.getComputedStyle(node).display;
|
||||
if (display === "none") display = "block";
|
||||
node.style.display = display;
|
||||
node.style.opacity = 0;
|
||||
node.style.transitionProperty = "opacity";
|
||||
node.style.transitionDuration = arg1 + "ms";
|
||||
window.setTimeout(() => {
|
||||
node.style.opacity = 1;
|
||||
window.setTimeout(() => {
|
||||
node.style.removeProperty("transition-property");
|
||||
node.style.removeProperty("transition-duration");
|
||||
node.style.removeProperty("opacity");
|
||||
}, arg1);
|
||||
|
||||
const uuid = uuidv4();
|
||||
node[uuid] = arg2;
|
||||
node[uuid](node);
|
||||
delete node[uuid];
|
||||
}, arg1);
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .hide()
|
||||
addEvent(nodeList, "hide", () => {
|
||||
nodeList.forEach((node) => {
|
||||
node.style.display = "none";
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .show()
|
||||
addEvent(nodeList, "show", () => {
|
||||
nodeList.forEach((node) => {
|
||||
if (node.style.display === "none") {
|
||||
node.style.display = "block";
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .clone()
|
||||
addEvent(nodeList, "clone", () => {
|
||||
let clonedNodes = [];
|
||||
nodeList.forEach((node) => {
|
||||
clonedNodes.push(node.cloneNode(true));
|
||||
});
|
||||
|
||||
addEvents(clonedNodes);
|
||||
return clonedNodes;
|
||||
});
|
||||
|
||||
// .each()
|
||||
addEvent(nodeList, "each", (arg1) => {
|
||||
nodeList.forEach((node, index) => {
|
||||
const uuid = uuidv4();
|
||||
node[uuid] = arg1;
|
||||
node[uuid](index, node);
|
||||
delete node[uuid];
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .find()
|
||||
addEvent(nodeList, "find", (arg1) => {
|
||||
let results = [];
|
||||
nodeList.forEach((node) => {
|
||||
const matchedNodes = node.querySelectorAll(arg1);
|
||||
if (matchedNodes.length) {
|
||||
matchedNodes.forEach((matchedNode) => {
|
||||
results.push(matchedNode);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(results);
|
||||
return results;
|
||||
});
|
||||
|
||||
// .hasClass()
|
||||
addEvent(nodeList, "hasClass", (arg1) => {
|
||||
let found = false;
|
||||
nodeList.forEach((node) => {
|
||||
if (node.classList.contains(arg1)) found = true;
|
||||
});
|
||||
return found;
|
||||
});
|
||||
|
||||
// .removeClass()
|
||||
addEvent(nodeList, "removeClass", (arg1) => {
|
||||
if (arg1.length) {
|
||||
arg1.split(" ").forEach((classname) => {
|
||||
nodeList.forEach((node) => {
|
||||
node.classList.remove(classname);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .addClass()
|
||||
addEvent(nodeList, "addClass", (arg1) => {
|
||||
if (arg1.length) {
|
||||
arg1.split(" ").forEach((classname) => {
|
||||
nodeList.forEach((node) => {
|
||||
node.classList.add(classname);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .is()
|
||||
addEvent(nodeList, "is", (arg1) => {
|
||||
if (typeof arg1 === "string") {
|
||||
return (
|
||||
nodeList[0].matches ||
|
||||
nodeList[0].matchesSelector ||
|
||||
nodeList[0].msMatchesSelector ||
|
||||
nodeList[0].mozMatchesSelector ||
|
||||
nodeList[0].webkitMatchesSelector ||
|
||||
nodeList[0].oMatchesSelector
|
||||
).call(nodeList[0], arg1);
|
||||
} else {
|
||||
return nodeList[0] === arg1;
|
||||
}
|
||||
});
|
||||
|
||||
// .attr()
|
||||
addEvent(nodeList, "attr", (arg1, arg2) => {
|
||||
if (arg2 === undefined && typeof arg1 !== "object") {
|
||||
if (nodeList[0] !== undefined) {
|
||||
const attr = nodeList[0].getAttribute(arg1);
|
||||
return attr === null ? undefined : attr;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
nodeList.forEach((node) => {
|
||||
if (typeof arg1 === "object") {
|
||||
for (const [key, val] of Object.entries(arg1)) {
|
||||
node.setAttribute(key, val);
|
||||
}
|
||||
} else {
|
||||
node.setAttribute(arg1, arg2);
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .removeAttr()
|
||||
addEvent(nodeList, "removeAttr", (arg1) => {
|
||||
nodeList.forEach((node) => {
|
||||
node.removeAttribute(arg1);
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .data()
|
||||
addEvent(nodeList, "data", (arg1, arg2) => {
|
||||
if (arg2 === undefined) {
|
||||
const attr = nodeList[0].getAttribute(`data-${arg1}`);
|
||||
return attr === null ? undefined : attr;
|
||||
}
|
||||
|
||||
nodeList.forEach((node) => {
|
||||
node.setAttribute(`data-${arg1}`, arg2);
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .width()
|
||||
addEvent(nodeList, "width", (arg1) => {
|
||||
if (arg1 === undefined) {
|
||||
if (nodeList === window) {
|
||||
return parseInt(window.innerWidth);
|
||||
} else {
|
||||
return typeof nodeList[0] !== "undefined"
|
||||
? parseInt(getComputedStyle(nodeList[0])["width"])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeList === window) {
|
||||
window.resizeTo(arg1, window.innerHeight);
|
||||
} else {
|
||||
nodeList.forEach((node) => {
|
||||
node.style["width"] = arg1;
|
||||
});
|
||||
}
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .height()
|
||||
addEvent(nodeList, "height", (arg1) => {
|
||||
if (arg1 === undefined) {
|
||||
if (nodeList === window) {
|
||||
return parseInt(window.innerHeight);
|
||||
} else {
|
||||
return typeof nodeList[0] !== "undefined"
|
||||
? parseInt(getComputedStyle(nodeList[0])["height"])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeList === window) {
|
||||
window.resizeTo(window.innerWidth, arg1);
|
||||
} else {
|
||||
nodeList.forEach((node) => {
|
||||
node.style["height"] = arg1;
|
||||
});
|
||||
}
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .css()
|
||||
addEvent(nodeList, "css", (arg1, arg2) => {
|
||||
if (arg2 === undefined && typeof arg1 !== "object") {
|
||||
return getComputedStyle(nodeList[0])[arg1];
|
||||
}
|
||||
|
||||
nodeList.forEach((node) => {
|
||||
if (typeof arg1 === "object") {
|
||||
for (const [key, val] of Object.entries(arg1)) {
|
||||
node.style[key] = val;
|
||||
}
|
||||
} else {
|
||||
node.style[arg1] = arg2;
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .replaceWith()
|
||||
addEvent(nodeList, "replaceWith", (arg1) => {
|
||||
const replacerNodes = [];
|
||||
const argumentTypes = getNodeList(arg1);
|
||||
|
||||
nodeList.forEach((node, key) => {
|
||||
argumentTypes.forEach((replacerNode) => {
|
||||
let dom = replacerNode;
|
||||
if (key > 0) {
|
||||
dom = replacerNode.cloneNode(true);
|
||||
}
|
||||
|
||||
node.parentNode.insertBefore(dom, node.nextSibling);
|
||||
replacerNodes.push(dom);
|
||||
});
|
||||
node.remove();
|
||||
});
|
||||
|
||||
addEvents(replacerNodes);
|
||||
return replacerNodes;
|
||||
});
|
||||
|
||||
// .insertAfter()
|
||||
addEvent(nodeList, "insertAfter", (arg1) => {
|
||||
const insertedNodes = [];
|
||||
const argumentTypes = getNodeList(arg1);
|
||||
|
||||
nodeList.forEach((node) => {
|
||||
argumentTypes.forEach((targetNode, key) => {
|
||||
let dom = node;
|
||||
if (key > 0) {
|
||||
dom = node.cloneNode(true);
|
||||
}
|
||||
|
||||
targetNode.parentNode.insertBefore(dom, targetNode.nextSibling);
|
||||
insertedNodes.push(dom);
|
||||
});
|
||||
});
|
||||
|
||||
addEvents(insertedNodes);
|
||||
return insertedNodes;
|
||||
});
|
||||
|
||||
// .appendTo()
|
||||
addEvent(nodeList, "appendTo", (arg1) => {
|
||||
const appendedNodes = [];
|
||||
const argumentTypes = getNodeList(arg1);
|
||||
|
||||
nodeList.forEach((node) => {
|
||||
argumentTypes.forEach((targetNode, key) => {
|
||||
let dom = node;
|
||||
if (key > 0) {
|
||||
dom = node.cloneNode(true);
|
||||
}
|
||||
|
||||
targetNode.appendChild(dom);
|
||||
appendedNodes.push(dom);
|
||||
});
|
||||
});
|
||||
|
||||
addEvents(appendedNodes);
|
||||
return appendedNodes;
|
||||
});
|
||||
|
||||
// .append()
|
||||
addEvent(nodeList, "append", (arg1) => {
|
||||
const argumentTypes = getNodeList(arg1);
|
||||
|
||||
nodeList.forEach((node, key) => {
|
||||
argumentTypes.forEach((appendNode) => {
|
||||
let dom = appendNode;
|
||||
if (key > 0) {
|
||||
dom = appendNode.cloneNode(true);
|
||||
}
|
||||
|
||||
node.appendChild(dom);
|
||||
});
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .remove()
|
||||
addEvent(nodeList, "remove", () => {
|
||||
nodeList.forEach((node) => {
|
||||
if (node.parentNode !== null) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .first()
|
||||
addEvent(nodeList, "first", () => {
|
||||
const firstNode = nodeList[0] !== undefined ? [nodeList[0]] : [];
|
||||
addEvents(firstNode);
|
||||
return firstNode;
|
||||
});
|
||||
|
||||
// .last()
|
||||
addEvent(nodeList, "last", () => {
|
||||
const lastNodeList =
|
||||
nodeList[nodeList.length - 1] !== undefined
|
||||
? [nodeList[nodeList.length - 1]]
|
||||
: [];
|
||||
|
||||
addEvents(lastNodeList);
|
||||
return lastNodeList;
|
||||
});
|
||||
|
||||
// .val()
|
||||
addEvent(nodeList, "val", (arg1) => {
|
||||
if (arg1 === undefined) {
|
||||
if (nodeList[0] instanceof HTMLSelectElement && nodeList[0].multiple) {
|
||||
const selectedOptions = [];
|
||||
for (const selectedOption of nodeList[0].selectedOptions) {
|
||||
selectedOptions.push(selectedOption.value);
|
||||
}
|
||||
return selectedOptions;
|
||||
} else {
|
||||
return nodeList[0].value;
|
||||
}
|
||||
}
|
||||
|
||||
nodeList.forEach((node) => {
|
||||
if (node instanceof HTMLSelectElement) {
|
||||
node.value = "";
|
||||
|
||||
if (typeof arg1 !== "object") {
|
||||
arg1 = [arg1];
|
||||
}
|
||||
|
||||
for (const value of arg1) {
|
||||
const selectedOption = Array.from(node).find(
|
||||
(option) => option.value == value
|
||||
);
|
||||
|
||||
if (selectedOption !== undefined) {
|
||||
selectedOption.selected = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.value = arg1;
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .html()
|
||||
addEvent(nodeList, "html", (arg1) => {
|
||||
if (arg1 === undefined) {
|
||||
return nodeList[0].innerHTML;
|
||||
}
|
||||
|
||||
nodeList.forEach((node) => {
|
||||
node.innerHTML = arg1;
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .text()
|
||||
addEvent(nodeList, "text", (arg1) => {
|
||||
if (arg1 === undefined) {
|
||||
return nodeList[0].textContent;
|
||||
}
|
||||
|
||||
nodeList.forEach((node) => {
|
||||
node.textContent = arg1;
|
||||
});
|
||||
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
|
||||
// .filter()
|
||||
addEvent(nodeList, "filter", (arg1) => {
|
||||
let results = [];
|
||||
nodeList.forEach((node, index) => {
|
||||
const uuid = uuidv4();
|
||||
node[uuid] = arg1;
|
||||
const filter = node[uuid](index, node);
|
||||
delete node[uuid];
|
||||
|
||||
if (filter) {
|
||||
results.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(results);
|
||||
return results;
|
||||
});
|
||||
|
||||
// .closest()
|
||||
addEvent(nodeList, "closest", (arg1) => {
|
||||
let results = [];
|
||||
nodeList.forEach((node) => {
|
||||
const matchedNode = node.closest(arg1);
|
||||
if (
|
||||
matchedNode !== null &&
|
||||
!results.filter((resNode) => resNode === matchedNode).length
|
||||
)
|
||||
results.push(matchedNode);
|
||||
});
|
||||
|
||||
addEvents(results);
|
||||
return results;
|
||||
});
|
||||
|
||||
// .children()
|
||||
addEvent(nodeList, "children", (arg1) => {
|
||||
let results = [];
|
||||
|
||||
nodeList.forEach((node) => {
|
||||
for (const matchedNode of node.children) {
|
||||
if (arg1 === undefined) {
|
||||
results.push(matchedNode);
|
||||
} else {
|
||||
for (const childNode of node.querySelectorAll(arg1)) {
|
||||
if (childNode === matchedNode) results.push(childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(results);
|
||||
return results;
|
||||
});
|
||||
|
||||
// .parent()
|
||||
addEvent(nodeList, "parent", () => {
|
||||
let results = [];
|
||||
nodeList.forEach((node) => {
|
||||
const matchedNode = node.parentNode;
|
||||
if (
|
||||
matchedNode !== null &&
|
||||
!results.filter((resNode) => resNode === matchedNode).length
|
||||
)
|
||||
results.push(matchedNode);
|
||||
});
|
||||
|
||||
addEvents(results);
|
||||
return results;
|
||||
});
|
||||
|
||||
// .prev()
|
||||
addEvent(nodeList, "prev", () => {
|
||||
let results = [];
|
||||
nodeList.forEach((node) => {
|
||||
if (node.previousElementSibling !== null) {
|
||||
results.push(node.previousElementSibling);
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(results);
|
||||
return results;
|
||||
});
|
||||
|
||||
// .next()
|
||||
addEvent(nodeList, "next", () => {
|
||||
let results = [];
|
||||
nodeList.forEach((node) => {
|
||||
if (node.nextElementSibling !== null) {
|
||||
results.push(node.nextElementSibling);
|
||||
}
|
||||
});
|
||||
|
||||
addEvents(results);
|
||||
return results;
|
||||
});
|
||||
};
|
||||
|
||||
return (window.dom = (arg) => {
|
||||
const nodeList = getNodeList(arg);
|
||||
addEvents(nodeList);
|
||||
return nodeList;
|
||||
});
|
||||
})();
|
||||
179
live_monitor_vue/src/helpers/dropdown.js
Executable file
179
live_monitor_vue/src/helpers/dropdown.js
Executable file
@@ -0,0 +1,179 @@
|
||||
import * as Popper from "@popperjs/core";
|
||||
import dom from "./dom";
|
||||
|
||||
(function (dom) {
|
||||
"use strict";
|
||||
function hide() {
|
||||
dom(".dropdown-menu").each(async function () {
|
||||
if (
|
||||
dom(this).attr("id") !== undefined &&
|
||||
dom('[data-dropdown-replacer="' + dom(this).attr("id") + '"]').length &&
|
||||
dom(this).data("dropdown-programmatically") === undefined
|
||||
) {
|
||||
let randId = dom(this).attr("id");
|
||||
let dropdownToggle = dom('[data-dropdown-replacer="' + randId + '"]')
|
||||
.parent()
|
||||
.find("[data-tw-toggle='dropdown']");
|
||||
|
||||
dom(this).removeClass("show");
|
||||
const event = new Event("hide.tw.dropdown");
|
||||
dom(dropdownToggle).parent()[0].dispatchEvent(event);
|
||||
|
||||
await setTimeout(() => {
|
||||
dom('[data-dropdown-replacer="' + randId + '"]').replaceWith(this);
|
||||
|
||||
dom(this).removeAttr("style");
|
||||
dom(this).removeAttr("data-popper-placement");
|
||||
|
||||
dom(dropdownToggle).attr("aria-expanded", false);
|
||||
|
||||
const event = new Event("hidden.tw.dropdown");
|
||||
dom(dropdownToggle).parent()[0].dispatchEvent(event);
|
||||
}, 200);
|
||||
} else if (
|
||||
dom(this).attr("id") !== undefined &&
|
||||
!dom('[data-dropdown-replacer="' + dom(this).attr("id") + '"]')
|
||||
.length &&
|
||||
dom(this).hasClass("show") &&
|
||||
dom(this).data("dropdown-programmatically") === undefined
|
||||
) {
|
||||
dom(this).remove();
|
||||
} else if (dom(this).data("dropdown-programmatically") == "initiate") {
|
||||
dom(this).attr("data-dropdown-programmatically", "showed");
|
||||
} else if (dom(this).data("dropdown-programmatically") == "showed") {
|
||||
dom(this).removeAttr("data-dropdown-programmatically");
|
||||
|
||||
hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findVisibleDropdownToggle(dropdownToggle) {
|
||||
return dropdownToggle.filter((key, dropdownToggle) => {
|
||||
return dropdownToggle.offsetParent !== null;
|
||||
});
|
||||
}
|
||||
|
||||
async function show(dropdown) {
|
||||
let dropdownBox = dom(dropdown).find(".dropdown-menu").first();
|
||||
let dropdownToggle = findVisibleDropdownToggle(
|
||||
dom(dropdown).find("[data-tw-toggle='dropdown']")
|
||||
);
|
||||
let placement = dom(dropdown).data("tw-placement")
|
||||
? dom(dropdown).data("tw-placement")
|
||||
: "bottom-end";
|
||||
let randId = "_" + Math.random().toString(36).substr(2, 9);
|
||||
|
||||
hide();
|
||||
|
||||
if (dom(dropdownBox).length) {
|
||||
dom(dropdownToggle).attr("aria-expanded", true);
|
||||
|
||||
dom(dropdown).css("position") == "static"
|
||||
? dom(dropdown).css("position", "relative")
|
||||
: "";
|
||||
|
||||
dom(dropdownBox).css("width", dom(dropdownBox).css("width").localeCompare("300px") ? "300px": dom(dropdownBox).css("width"));
|
||||
|
||||
dom('<div data-dropdown-replacer="' + randId + '"></div>').insertAfter(
|
||||
dropdownBox
|
||||
);
|
||||
dom(dropdownBox).attr("id", randId).appendTo("body");
|
||||
|
||||
dom(".modal.show").each(function () {
|
||||
if (dom(this).find('[data-dropdown-replacer="' + randId + '"]')) {
|
||||
dom(dropdownBox).css("z-index", dom(this).css("z-index"));
|
||||
}
|
||||
});
|
||||
|
||||
Popper.createPopper(dropdownToggle[0], dropdownBox[0], {
|
||||
placement: placement,
|
||||
});
|
||||
|
||||
dom(dropdownBox).addClass("show");
|
||||
|
||||
const event = new Event("show.tw.dropdown");
|
||||
dom(dropdown)[0].dispatchEvent(event);
|
||||
|
||||
await setTimeout(() => {
|
||||
const event = new Event("shown.tw.dropdown");
|
||||
dom(dropdown)[0].dispatchEvent(event);
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleProgrammatically(dropdown) {
|
||||
let dropdownBox = dom(dropdown).find(".dropdown-menu").first();
|
||||
if (dom(dropdownBox).length) {
|
||||
showProgrammatically(dropdown);
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
function showProgrammatically(dropdown) {
|
||||
if (dom(dropdown).find(".dropdown-menu").length) {
|
||||
dom(dropdown)
|
||||
.find(".dropdown-menu")
|
||||
.attr("data-dropdown-programmatically", "initiate");
|
||||
} else {
|
||||
let randId = dom("[data-dropdown-replacer]").data("dropdown-replacer");
|
||||
dom("#" + randId).attr("data-dropdown-programmatically", "initiate");
|
||||
}
|
||||
|
||||
show(dropdown);
|
||||
}
|
||||
function createInstance(dropdownToggle) {
|
||||
const dropdown = dom(dropdownToggle).closest(".dropdown");
|
||||
return {
|
||||
show() {
|
||||
showProgrammatically(dropdown);
|
||||
},
|
||||
hide() {
|
||||
hide();
|
||||
},
|
||||
toggle() {
|
||||
toggleProgrammatically(dropdown);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
dom("body").on("click", function (event) {
|
||||
let dropdown = dom(event.target).closest(".dropdown");
|
||||
let dropdownToggle = dom(dropdown).find("[data-tw-toggle='dropdown']");
|
||||
let dropdownBox = dom(dropdown).find(".dropdown-menu").first();
|
||||
let activeDropdownBox = dom(event.target).closest(".dropdown-menu").first();
|
||||
let dismissButton = dom(event.target).data("tw-dismiss");
|
||||
|
||||
if (
|
||||
(!dom(dropdown).length && !dom(activeDropdownBox).length) ||
|
||||
(dom(dropdownToggle).length && !dom(dropdownBox).length) ||
|
||||
dismissButton == "dropdown"
|
||||
) {
|
||||
hide();
|
||||
} else if (!dom(activeDropdownBox).length) {
|
||||
show(dropdown);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.code == "Escape") {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
(function init() {
|
||||
dom("[data-tw-toggle='dropdown']").each(function () {
|
||||
this["__dropdown"] = createInstance(this);
|
||||
});
|
||||
|
||||
if (window.tailwind === undefined) window.tailwind = {};
|
||||
window.tailwind.Dropdown = {
|
||||
getInstance(el) {
|
||||
return el.__dropdown;
|
||||
},
|
||||
getOrCreateInstance(el) {
|
||||
return el.__dropdown === undefined ? createInstance(el) : el.__dropdown;
|
||||
},
|
||||
};
|
||||
})();
|
||||
})(dom);
|
||||
20
live_monitor_vue/src/helpers/examle.vue
Normal file
20
live_monitor_vue/src/helpers/examle.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Name',
|
||||
components: {},
|
||||
data() {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
55
live_monitor_vue/src/helpers/functions.js
Normal file
55
live_monitor_vue/src/helpers/functions.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { type } from 'ramda'
|
||||
|
||||
/* eslint-disable */
|
||||
export const runAfter = (func, wait, immediate) => {
|
||||
let timeout
|
||||
return function () {
|
||||
const context = this
|
||||
const args = arguments
|
||||
const later = function () {
|
||||
timeout = null
|
||||
if (!immediate) func.apply(context, args)
|
||||
}
|
||||
const callNow = immediate && !timeout
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(later, wait)
|
||||
if (callNow) func.apply(context, args)
|
||||
}
|
||||
}
|
||||
|
||||
export const pipe = (x) => (...fns) => fns.reduce((r, fn) => fn(r), x)
|
||||
|
||||
export const capitalizeFirstLetter = (string) => {
|
||||
if (string) return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
else {
|
||||
const error = `error by func capitalizeFirstLetter, ivalid string. string can't equal ${string}`
|
||||
console.error(error)
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
export const lowerFirstLetter = (string) => {
|
||||
if (string && string[0] && type(string) === "String") return string[0].toLowerCase() + string.slice(1);
|
||||
else {
|
||||
const error = `error by func lowerFirstLetter, ivalid string. string can't equal ${string}`
|
||||
console.error(error)
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
export const substringIfNeed = (string, length = 40) => {
|
||||
if (string && `${string}`.length > length) {
|
||||
return `${string}`.substring(0, length) + "...";
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
export const copyObj = (mainObj) => {
|
||||
let objCopy = {};
|
||||
let key;
|
||||
|
||||
for (key in mainObj) {
|
||||
objCopy[key] = mainObj[key];
|
||||
}
|
||||
return objCopy;
|
||||
}
|
||||
15
live_monitor_vue/src/main.js
Normal file
15
live_monitor_vue/src/main.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createApp } from 'vue'
|
||||
import "./styles/_index.css"
|
||||
import 'remixicon/fonts/remixicon.css'
|
||||
import App from './App.vue'
|
||||
import Router from "@router"
|
||||
import ramdaVue from "ramda-vue";
|
||||
import { store } from '@/store'
|
||||
import "./helpers/dropdown.js"
|
||||
|
||||
|
||||
createApp(App)
|
||||
.use(Router)
|
||||
.use(ramdaVue)
|
||||
.use(store)
|
||||
.mount('#app')
|
||||
152
live_monitor_vue/src/router/index.js
Normal file
152
live_monitor_vue/src/router/index.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import {createRouter, createWebHistory} from "vue-router";
|
||||
import Main from "@pages/Main/index.vue"
|
||||
import Auth from "@pages/Auth/index.vue"
|
||||
import Communication from "@pages/Communication/index.vue"
|
||||
import LastPacksNum from "@pages/LastPacksNum/index.vue"
|
||||
import Fuel from "@pages/Fuel/index.vue"
|
||||
import Cron from "@pages/Cron/index.vue"
|
||||
import ICCID from "@pages/ICCID/index.vue"
|
||||
import Devices from "@pages/Devices/index.vue"
|
||||
import FreePacks from "@pages/FreePacks/index.vue"
|
||||
import Packs from "@pages/Packs/index.vue"
|
||||
import FileViewer from "@pages/FileViewer/index.vue"
|
||||
import NotFound from "@pages/404/index.vue"
|
||||
import Diap from "@admin_pages/Diap/index.vue"
|
||||
import News from "@admin_pages/News/index.vue"
|
||||
import Machines from "@pages/Main/Machines/Machines.vue"
|
||||
import FinderPacks from "@pages/Main/FinderPacks/FinderPacks.vue"
|
||||
import AddUsers from "@pages/Main/AddUsers/AddUsers.vue"
|
||||
import Services from "@admin_pages/Services/index.vue"
|
||||
import ServiceManage from "@admin_pages/ServiceManage/index.vue"
|
||||
import {get} from "@store/modules/apiHelpers"
|
||||
import {cond, T} from 'ramda'
|
||||
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "main",
|
||||
component: Main,
|
||||
children: [
|
||||
{
|
||||
path: "/",
|
||||
name: "machines",
|
||||
component: Machines
|
||||
},
|
||||
{
|
||||
path: "finder_packs",
|
||||
name: "finder_packs",
|
||||
component: FinderPacks
|
||||
},
|
||||
{
|
||||
path: "add_users",
|
||||
name: "add_users",
|
||||
component: AddUsers
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/html/last_packs",
|
||||
name: "last_packs",
|
||||
component: import("@pages/LastPacks/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/html/controll_com_service",
|
||||
name: "communication",
|
||||
component: import("@pages/Communication/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/html/last_packs_by_pack",
|
||||
name: "last_packs_by_pack",
|
||||
component: LastPacksNum
|
||||
},
|
||||
{
|
||||
path: "/html/live_fuel",
|
||||
name: "fuel",
|
||||
component: Fuel
|
||||
},
|
||||
{
|
||||
path: "/html/cron",
|
||||
name: "cron",
|
||||
component: Cron
|
||||
},
|
||||
{
|
||||
path: "/html/fetch_iccid",
|
||||
name: "iccid",
|
||||
component: ICCID
|
||||
},
|
||||
{
|
||||
path: "/html/fetch_askr_devices",
|
||||
name: "devices",
|
||||
component: Devices
|
||||
},
|
||||
{
|
||||
path: "/html/packs/:pack",
|
||||
name: "packs",
|
||||
component: Packs
|
||||
},
|
||||
{
|
||||
path: "/html/free_packs",
|
||||
name: "free_packs",
|
||||
component: FreePacks
|
||||
},
|
||||
{
|
||||
path: "/html/file_viewer/:mode",
|
||||
name: "file_viewer",
|
||||
component: FileViewer
|
||||
},
|
||||
{
|
||||
path: "/admin_panel/diap",
|
||||
name: "diap",
|
||||
component: Diap
|
||||
},
|
||||
{
|
||||
path: "/admin_panel/services",
|
||||
name: "services",
|
||||
component: Services
|
||||
},
|
||||
{
|
||||
path: "/users/log_out",
|
||||
name: "auth",
|
||||
component: Auth
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: '404',
|
||||
component: NotFound
|
||||
},
|
||||
];
|
||||
|
||||
const routerHistory = createWebHistory();
|
||||
|
||||
const router = createRouter({
|
||||
history: routerHistory,
|
||||
routes
|
||||
});
|
||||
|
||||
const logout = (from, next) => {
|
||||
localStorage.removeItem("token")
|
||||
sessionStorage.setItem("pathname", from.fullPath)
|
||||
next()
|
||||
}
|
||||
|
||||
const auth_check = async () => {
|
||||
return await get("/api/auth/loginStatus", ({data}) => {
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
// router.beforeEach(async (to, from, next) => {
|
||||
// const status = await auth_check()
|
||||
|
||||
// return cond([
|
||||
// [() => to.name === "auth" && from.name === "auth", () => next()],
|
||||
// [() => to.name === "auth", () => logout(from, () => next())],
|
||||
// [() => !status, () => logout(from, () => next({name: "auth"}))],
|
||||
// [T, () => next()],
|
||||
// ])()
|
||||
// })
|
||||
|
||||
|
||||
export {routes};
|
||||
export default router;
|
||||
54
live_monitor_vue/src/store/hooks/Alert.js
Normal file
54
live_monitor_vue/src/store/hooks/Alert.js
Normal file
@@ -0,0 +1,54 @@
|
||||
export class Alert {
|
||||
constructor({ id, color, icon, message, delay = 4000, isManualClose, width = 'auto' }) {
|
||||
|
||||
const wrapper = document.createElement("div");
|
||||
const alert = document.createElement("div");
|
||||
wrapper.appendChild(alert)
|
||||
|
||||
const closeIcon = document.createElement("i");
|
||||
closeIcon.className = `ri-close-circle-fill cursor-pointer text-${color}-800 text-xl`;
|
||||
closeIcon.onclick = () => {
|
||||
this.destroy()
|
||||
}
|
||||
|
||||
alert.className = `bg-${color}-100 bg-light shadow-md rounded-lg py-5 px-6 mb-3 text-base text-${color}-700 inline-flex items-center fixed transition_up top-0 translate-y-4 left-0 w-fit slideInFromTop border border-${color}-300 gap-4 flex items-center`;
|
||||
alert.style.zIndex = "10000";
|
||||
alert.style.width = width
|
||||
alert.style.transform = "translateY(-5rem) translateX(calc(50vw - 50%))";
|
||||
alert.setAttribute("role", "alert");
|
||||
alert.innerHTML = `
|
||||
<div class="w-fit gap-1 flex items-center">
|
||||
${icon}
|
||||
${typeof message === 'string' ? message : JSON.stringify(message)}
|
||||
</div>
|
||||
`;
|
||||
alert.appendChild(closeIcon);
|
||||
document.getElementById(id).appendChild(wrapper);
|
||||
|
||||
setTimeout(() => {
|
||||
if (delay > 100 && alert) {
|
||||
alert.style.transform =
|
||||
"translateY(1.25rem) translateX(calc(50vw - 50%))";
|
||||
}
|
||||
}, 300);
|
||||
|
||||
setTimeout(() => {
|
||||
if(!isManualClose) {
|
||||
this.destroy()
|
||||
}
|
||||
}, delay)
|
||||
|
||||
this.alert = alert;
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
destroy() {
|
||||
if (this.alert) {
|
||||
this.alert.style.transform =
|
||||
"translateY(-5rem) translateX(calc(50vw - 50%))";
|
||||
setTimeout(() => {
|
||||
this.wrapper.innerHTML = "";
|
||||
this.alert = null;
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
live_monitor_vue/src/store/hooks/CopyToClipboard.js
Normal file
53
live_monitor_vue/src/store/hooks/CopyToClipboard.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Alert } from "@store/hooks/Alert"
|
||||
|
||||
export class CopyToClipboard {
|
||||
constructor (id, text) {
|
||||
this.id = id
|
||||
this.el = document.getElementById(id)
|
||||
this.text = this.el?.getAttribute('copyOnClickText')
|
||||
this.clipBoardText = text
|
||||
|
||||
if (this.el && this.clipBoardText && !this.text) {
|
||||
this.copy(this.clipBoardText, this.id)
|
||||
}
|
||||
|
||||
if (this.el && this.text && !this.clipBoardText) {
|
||||
this.copy(this.text, this.id)
|
||||
}
|
||||
}
|
||||
|
||||
copy (text, id) {
|
||||
if (!navigator.clipboard) {
|
||||
fallbackCopyTextToClipboard(text)
|
||||
return
|
||||
}
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
new Alert({id: id, color: "green", icon: `<i class="ri-book-fill"></i>`, message: "Текст скопирован", delay: 2000})
|
||||
}, function(err) {
|
||||
new Alert({id: id, color: "yellow", icon: `<i class="ri-book-fill"></i>`, message: "Ваш браузер не поддерживает копирование", delay: 2000})
|
||||
console.error('Async: Could not copy text: ', err)
|
||||
})
|
||||
}
|
||||
|
||||
fallbackCopyTextToClipboard (text, id) {
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
textArea.style.top = "-2000px";
|
||||
textArea.style.left = "-2000px";
|
||||
textArea.style.position = "fixed";
|
||||
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
new Alert({id: id, color: "green", icon: `<i class="ri-book-fill"></i>`, message: "Текст скопирован", delay: 2000})
|
||||
|
||||
} catch (err) {
|
||||
new Alert({id: id, color: "yellow", icon: `<i class="ri-book-fill"></i>`, message: "Ваш браузер не поддерживает копирование", delay: 2000})
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
}
|
||||
110
live_monitor_vue/src/store/hooks/Echarts/graphicElements.js
Normal file
110
live_monitor_vue/src/store/hooks/Echarts/graphicElements.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import {groupByKeys} from "./staticData"
|
||||
import {getTextWidth, isEqualKeyValue} from "./helpers"
|
||||
|
||||
const viewGraphicsPieBar = (chartId, period = '', countMachines, title, windowConfig, isOpenMenu = false, type) => {
|
||||
|
||||
const elements = []
|
||||
const elementsMenu = []
|
||||
|
||||
const elPeriod = period ? {id: 'period', name: 'Период загрузки данных', type: 'text', right: windowConfig?.pie?.graphic?.period?.right, top: windowConfig?.pie?.graphic?.period?.top, z: 999, style: {text: period, fill: '#777', fontSize: windowConfig?.pie?.graphic?.period?.fontSize}} : ''
|
||||
|
||||
elements.push(elPeriod)
|
||||
|
||||
const elCount = {id: 'count', type: 'text', left: windowConfig?.pie?.graphic?.count?.left, top: windowConfig?.pie?.graphic?.count?.top, z: 999, style: {text: countMachines, textAlign: 'center', fontSize: windowConfig?.pie?.graphic?.count?.fontSize}}
|
||||
|
||||
!chartId.includes('bar') ? elements.push(elCount) : ''
|
||||
|
||||
const elMenu = {id: 'menu', info: {typeEl: 'menu', chartId: chartId, type: type}, type: 'image', right: windowConfig?.bar?.graphic?.menu?.right, top: windowConfig?.bar?.graphic?.menu?.top, z: 999, style: {image: '/images/charts-more-vertical.svg', width: 17, height: 17}}
|
||||
|
||||
elements.push(elMenu)
|
||||
|
||||
const elRect = {id: 'rect', info: {typeEl: 'rect'}, type: 'rect', right: windowConfig?.bar?.graphic?.rect?.right, top: windowConfig?.bar?.graphic?.rect?.top, z: 999, shape: {width: 90, height: 40, r: [5]}, style: {fill: '#eee', shadowBlur: 8, shadowOffsetX: 3, shadowOffsetY: 3, shadowColor: 'rgba(0,0,0,0.3)'}}
|
||||
|
||||
const elDownload = {id: 'download', info: {typeEl: 'download', title: title, countCharts: countMachines, chartId: chartId, type: type}, type: 'image', right: windowConfig?.bar?.graphic?.download?.right, top: windowConfig?.bar?.graphic?.download?.top, z: 999, style: {image: '/images/charts-download.svg', width: 17, height: 17}}
|
||||
|
||||
const elDownloadTitle = {id: 'downloadTitle', info: {typeEl: 'downloadTitle', title: title, countCharts: countMachines, chartId: chartId, type: type}, type: 'text', right: windowConfig?.bar?.graphic?.downloadTitle?.right, top: windowConfig?.bar?.graphic?.downloadTitle?.top, z: 999, style: {text: 'Скачать', fontSize: 12}}
|
||||
|
||||
elementsMenu.push(elRect)
|
||||
elementsMenu.push(elDownload)
|
||||
elementsMenu.push(elDownloadTitle)
|
||||
|
||||
const menuContainer = {id: 'menuContainer', type: 'group', bounding: 'raw', right: windowConfig?.bar?.graphic?.container?.right, top: windowConfig?.bar?.graphic?.container?.top, z: 9999, children: elementsMenu, style: {}}
|
||||
|
||||
isOpenMenu ? elements.push(menuContainer) : ''
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
const viewGraphicsTypesBar = (chartId, title, groupByKey, prevGroupByData, filterKey, prevGroupByFilters, type, groupByValue, period, countCharts, windowConfig, isOpenMenu = false) => {
|
||||
|
||||
const elements = []
|
||||
const elementsMenu = []
|
||||
|
||||
const elPeriod = {id: 'period', name: 'Период загрузки данных', type: 'text', right: windowConfig?.bar?.graphic?.period?.right, top: windowConfig?.bar?.graphic?.period?.top, z: 999, style: {text: period, fill: '#777', fontSize: windowConfig?.pie?.graphic?.period?.fontSize}}
|
||||
|
||||
elements.push(elPeriod)
|
||||
|
||||
const elMenu = {id: 'menu', info: {typeEl: 'menu', chartId: chartId, groupByKey: groupByKey, filterKey: filterKey, prevGroupByData: prevGroupByData, prevGroupByFilters: prevGroupByFilters, type: type, groupByValue: groupByValue}, type: 'image', right: windowConfig?.bar?.graphic?.menu?.right, top: windowConfig?.bar?.graphic?.menu?.top, z: 999, style: {image: '/images/charts-more-vertical.svg', width: 17, height: 17}}
|
||||
|
||||
elements.push(elMenu)
|
||||
|
||||
const elRect = {id: 'rect', info: {typeEl: 'rect'}, type: 'rect', right: windowConfig?.bar?.graphic?.rect?.right, top: windowConfig?.bar?.graphic?.rect?.top, z: 999, shape: {width: 90, height: 40, r: [5]}, style: {fill: '#eee', shadowBlur: 8, shadowOffsetX: 3, shadowOffsetY: 3, shadowColor: 'rgba(0,0,0,0.3)'}}
|
||||
|
||||
const elDownload = {id: 'download', info: {typeEl: 'download', title: title, countCharts: countCharts, chartId: chartId, groupByKey: groupByKey, filterKey: filterKey, prevGroupByData: prevGroupByData, prevGroupByFilters: prevGroupByFilters, type: type, groupByValue: groupByValue}, type: 'image', right: windowConfig?.bar?.graphic?.download?.right, top: windowConfig?.bar?.graphic?.download?.top, z: 999, style: {image: '/images/charts-download.svg', width: 17, height: 17}}
|
||||
|
||||
const elDownloadTitle = {id: 'downloadTitle', info: {typeEl: 'downloadTitle', title: title, countCharts: countCharts, chartId: chartId, groupByKey: groupByKey, filterKey: filterKey, prevGroupByData: prevGroupByData, prevGroupByFilters: prevGroupByFilters, type: type, groupByValue: groupByValue}, type: 'text', right: windowConfig?.bar?.graphic?.downloadTitle?.right, top: windowConfig?.bar?.graphic?.downloadTitle?.top, z: 999, style: {text: 'Скачать', fontSize: 12}}
|
||||
|
||||
elementsMenu.push(elRect)
|
||||
elementsMenu.push(elDownload)
|
||||
elementsMenu.push(elDownloadTitle)
|
||||
|
||||
const menuContainer = {id: 'menuContainer', type: 'group', bounding: 'raw', right: windowConfig?.bar?.graphic?.container?.right, top: windowConfig?.bar?.graphic?.container?.top, z: 9999, children: elementsMenu, style: {}}
|
||||
|
||||
isOpenMenu ? elements.push(menuContainer) : ''
|
||||
|
||||
if(groupByKey !== groupByKeys.firstLayout && !isEqualKeyValue(prevGroupByData, groupByKeys.firstLayout, null)) {
|
||||
|
||||
const buttonPrevious = {id: `${filterKey}`, info: {typeEl: 'buttonPrevious', prevGroupByData: prevGroupByData, prevGroupByFilters: prevGroupByFilters, type: type, groupByValue: groupByValue}, type: 'image', right: windowConfig?.bar?.graphic?.back?.right, top: windowConfig?.bar?.graphic?.back?.top, z: 999, style: {image: '/images/arrow-go-back-line.svg', width: 17, height: 17}}
|
||||
|
||||
elements.push(buttonPrevious)
|
||||
}
|
||||
const elMain = {id: '', info: {typeEl: 'link', filterKey: filterKey, prevGroupByData: prevGroupByData, prevGroupByFilters: prevGroupByFilters, type: type, groupByValue: groupByValue}, type: 'text', left: windowConfig?.bar?.graphic?.link?.left, top: windowConfig?.bar?.graphic?.link?.top, z: 999, style: {text: 'Главная', fontSize: 10}}
|
||||
const notFirstLayout = !Object.keys(prevGroupByFilters).includes(groupByKeys.firstLayout)
|
||||
|
||||
for (const groupByFilter in prevGroupByFilters) {
|
||||
if (groupByFilter === groupByKeys.firstLayout) {
|
||||
const linkTitle = `/ ${prevGroupByFilters[groupByKeys.firstLayout]}`
|
||||
const leftPos = getTextWidth('Главная ', 10)
|
||||
elMain.id = `0:link_${groupByFilter}-${linkTitle}`
|
||||
elements.push(elMain)
|
||||
const paramsFirst = {id: `1:link_${groupByFilter}-${linkTitle}`, info: {typeEl: 'link', filterKey: filterKey, prevGroupByData: prevGroupByData, prevGroupByFilters: prevGroupByFilters, type: type, groupByValue: groupByValue}, type: 'text', left: leftPos + windowConfig?.bar?.graphic?.link?.left, top: windowConfig?.bar?.graphic?.link?.top, z: 999, style: {text: linkTitle, fontSize: 10}}
|
||||
elements.push(paramsFirst)
|
||||
}
|
||||
if (groupByFilter === groupByKeys.secondLayout) {
|
||||
const linkTitle = ` / ${prevGroupByFilters[groupByKeys.secondLayout]}`
|
||||
const leftPos = getTextWidth(notFirstLayout ? 'Главная ' : `Главная / ${prevGroupByFilters[groupByKeys.firstLayout]}`, 10)
|
||||
if(notFirstLayout) {
|
||||
elMain.id = `0:link_${groupByFilter}-${linkTitle}`
|
||||
elements.push(elMain)
|
||||
}
|
||||
const paramsSecond = {id: `2:link_${groupByFilter}-${linkTitle}`, info: {typeEl: 'link', filterKey: filterKey, prevGroupByData: prevGroupByData, prevGroupByFilters: prevGroupByFilters, type: type, groupByValue: groupByValue}, type: 'text', left: leftPos + windowConfig?.bar?.graphic?.link?.left, top: windowConfig?.bar?.graphic?.link?.top, z: 999, style: {text: linkTitle, fontSize: 10}}
|
||||
elements.push(paramsSecond)
|
||||
}
|
||||
if (groupByFilter === groupByKeys.thirdLayout) {
|
||||
const linkTitle = ` / ${prevGroupByFilters[groupByKeys.thirdLayout]}`
|
||||
const leftPos = getTextWidth(notFirstLayout ? `Главная / ${prevGroupByFilters[groupByKeys.secondLayout]} ` : `Главная / ${prevGroupByFilters[groupByKeys.firstLayout]} / ${prevGroupByFilters[groupByKeys.secondLayout]}`, 10)
|
||||
const paramsThird = {id: `3:link_${groupByFilter}-${linkTitle}`, info: {typeEl: 'link', filterKey: filterKey, prevGroupByData: prevGroupByData, prevGroupByFilters: prevGroupByFilters, type: type, groupByValue: groupByValue}, type: 'text', left: leftPos + windowConfig?.bar?.graphic?.link?.left, top: windowConfig?.bar?.graphic?.link?.top, z: 999, style: {text: linkTitle, fontSize: 10}}
|
||||
elements.push(paramsThird)
|
||||
}
|
||||
if (groupByFilter === groupByKeys.fourthLayout) {
|
||||
const linkTitle = ` / ${prevGroupByFilters[groupByKeys.fourthLayout]}`
|
||||
const leftPos = getTextWidth(notFirstLayout ? `Главная / ${prevGroupByFilters[groupByKeys.secondLayout]} / ${prevGroupByFilters[groupByKeys.thirdLayout]} ` : `Главная / ${prevGroupByFilters[groupByKeys.firstLayout]} / ${prevGroupByFilters[groupByKeys.secondLayout]} / ${prevGroupByFilters[groupByKeys.thirdLayout]}`, 10)
|
||||
const paramsFourth = {id: `4:link_${groupByFilter}-${linkTitle}`, info: {typeEl: 'link', filterKey: filterKey, prevGroupByData: prevGroupByData, prevGroupByFilters: prevGroupByFilters, type: type, groupByValue: groupByValue}, type: 'text', left: leftPos + windowConfig?.bar?.graphic?.link?.left, top: windowConfig?.bar?.graphic?.link?.top, z: 999, style: {text: linkTitle, fontSize: 10}}
|
||||
elements.push(paramsFourth)
|
||||
}
|
||||
}
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
export {viewGraphicsPieBar, viewGraphicsTypesBar}
|
||||
157
live_monitor_vue/src/store/hooks/Echarts/helpers.js
Normal file
157
live_monitor_vue/src/store/hooks/Echarts/helpers.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import {groupByKeys} from './staticData'
|
||||
|
||||
const getTextWidth = (text, font) => {
|
||||
const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement('canvas'));
|
||||
const context = canvas.getContext('2d');
|
||||
context.font = font;
|
||||
const metrics = context.measureText(text);
|
||||
return metrics.width;
|
||||
}
|
||||
|
||||
const isEqualKeyValue = (obj, key, value) => {
|
||||
if(Object.keys(obj).length > 0) {
|
||||
if(Object.keys(obj).includes(key) && Object.values(obj).includes(value)) return true
|
||||
else return false
|
||||
} else return true
|
||||
}
|
||||
|
||||
const chartsFiltersNames = (data) => [...new Set(data.map((el) => el.charts.map((item) => item.name)).flat())]
|
||||
const chartsFiltersNamesMl = (data) => [...new Set(data.map((el) => el.map((item) => item.charts.name)).flat().filter(el => el !== undefined))]
|
||||
|
||||
const updateBarChartsData = (data, key) => data.map((el) => {
|
||||
return el.filter((item) => {
|
||||
if(item.name === key) {
|
||||
return el
|
||||
}
|
||||
})
|
||||
}).flat()
|
||||
|
||||
const dataFilterTitle = (data, title) => data.map((el) => {
|
||||
return el.reduce((acc, item) => {
|
||||
if(item.name === title || acc !== undefined && acc?.name === title) {
|
||||
if(acc === 0) {
|
||||
return {name: item.name, count: Number(item.value)}
|
||||
}
|
||||
if(acc?.count >= 0) {
|
||||
return {name: item.name, count: acc.count + Number(item.value)}
|
||||
}
|
||||
}
|
||||
}, 0)
|
||||
}).filter((el) => el !== undefined)
|
||||
|
||||
const prepareBarTypesData = (data, xAxisData, params) => {
|
||||
if(data.charts.length < chartsFiltersNames(xAxisData).length) {
|
||||
// Если нет фильтруемых данных, то добавляем столбец графика с нулевым значением для каждого элемента el
|
||||
const zeroData = chartsFiltersNames(xAxisData).filter((name) => !data.charts.some(({name: name2}) => name === name2)).map((name) => {
|
||||
return {filterKey: params.filterKey, name: name, value: 0}
|
||||
})
|
||||
const withoutZeroData = data.charts.map((item) => {
|
||||
item.type = params.type
|
||||
item.filterKey = params.filterKey
|
||||
item.groupByKey = params.groupByKey
|
||||
item.groupByValue = params.groupByValue ? params.groupByValue : params.typeId // Для графика типа "types" params.typeId
|
||||
item.groupByData = {[params.groupByKey]: data.yAxisData}
|
||||
item.prevGroupByData = params.prevGroupByFilters
|
||||
return item})
|
||||
return [withoutZeroData, zeroData].flat()
|
||||
} else {
|
||||
return data.charts.map((item) => {
|
||||
item.type = params.type
|
||||
item.filterKey = params.filterKey
|
||||
item.groupByKey = params.groupByKey
|
||||
item.groupByValue = params.groupByValue ? params.groupByValue : params.typeId
|
||||
item.groupByData = {[params.groupByKey]: data.yAxisData}
|
||||
item.prevGroupByData = params.prevGroupByFilters
|
||||
return item
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const prepareBarTypesMlData = (data, params) => {
|
||||
return data.map((item) => {
|
||||
// console.log('item', item)
|
||||
item.charts.type = params.type
|
||||
item.charts.groupByKey = params.groupByKey
|
||||
item.charts.groupByValue = params.groupByValue ? params.groupByValue : params.typeId
|
||||
item.charts.groupByData = {[params.groupByKey]: item.yAxisData}
|
||||
item.charts.prevGroupByData = params.prevGroupByFilters
|
||||
return item.charts
|
||||
})
|
||||
}
|
||||
|
||||
const groupByBarTypesData = (data, xAxisData, func) => {
|
||||
const dataGroupBy = []
|
||||
const dataCustomTitle = []
|
||||
|
||||
for(let i = 0; i < (func(xAxisData).length); i++) {
|
||||
dataGroupBy.push(updateBarChartsData(data, func(xAxisData)[i]))
|
||||
dataCustomTitle.push(dataFilterTitle(dataGroupBy, func(xAxisData)[i]))
|
||||
}
|
||||
|
||||
return {dataGroupBy: dataGroupBy, dataCustomTitle: dataCustomTitle}
|
||||
}
|
||||
|
||||
const updatedGroupByData = (idx, prevGroupByFilters, groupByKey, groupByValue) => {
|
||||
|
||||
switch(idx) {
|
||||
case '0': return {[groupByKey]: groupByValue}
|
||||
case '1':
|
||||
for (const prevGroupByFilter in prevGroupByFilters) {
|
||||
if(prevGroupByFilter === groupByKeys.secondLayout) {
|
||||
delete prevGroupByFilters[prevGroupByFilter]
|
||||
}
|
||||
if(prevGroupByFilter === groupByKeys.thirdLayout) {
|
||||
delete prevGroupByFilters[prevGroupByFilter]
|
||||
}
|
||||
if(prevGroupByFilter === groupByKeys.fourthLayout) {
|
||||
delete prevGroupByFilters[prevGroupByFilter]
|
||||
}
|
||||
}
|
||||
return prevGroupByFilters
|
||||
case '2':
|
||||
for (const prevGroupByFilter in prevGroupByFilters) {
|
||||
if(prevGroupByFilter === groupByKeys.thirdLayout) {
|
||||
delete prevGroupByFilters[prevGroupByFilter]
|
||||
}
|
||||
if(prevGroupByFilter === groupByKeys.fourthLayout) {
|
||||
delete prevGroupByFilters[prevGroupByFilter]
|
||||
}
|
||||
}
|
||||
return prevGroupByFilters
|
||||
case '3':
|
||||
for (const prevGroupByFilter in prevGroupByFilters) {
|
||||
if(prevGroupByFilter === groupByKeys.fourthLayout) {
|
||||
delete prevGroupByFilters[prevGroupByFilter]
|
||||
}
|
||||
}
|
||||
return prevGroupByFilters
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const choiceTypeBarTypesCharts = (params) => {
|
||||
switch(params.type) {
|
||||
case 'ml':
|
||||
const yAxisMlData = params.yAxisData.sort().map((el) => {
|
||||
return `${el.key !== null ? el.key : 'Нет данных'} ${el.val}`
|
||||
}).sort((a, b) => a - b).reverse()
|
||||
const xAxisMlData = params.xAxisData.map((item) => item.map((el) => el))
|
||||
const mlData = xAxisMlData.sort().map((el) => prepareBarTypesMlData(el, params))
|
||||
return {axisData: yAxisMlData, updatedData: groupByBarTypesData(mlData, xAxisMlData, chartsFiltersNamesMl)}
|
||||
|
||||
case 'mount':
|
||||
const xAxisMountData = params.yAxisData.sort((a, b) => a.key - b.key).map((el) => {
|
||||
return `${el.key !== null ? el.key : 'Нет данных'} \n ${el.val}`})
|
||||
const mountData = params.xAxisData.sort().map((el) => prepareBarTypesData(el, params.xAxisData, params))
|
||||
return {axisData: xAxisMountData, updatedData: groupByBarTypesData(mountData, params.xAxisData, chartsFiltersNames)}
|
||||
|
||||
default :
|
||||
const yAxisDefaultData = params.yAxisData.sort().map((el) => {
|
||||
return `${el.key !== null ? el.key : 'Нет данных'} ${el.val}`
|
||||
}).sort((a, b) => a - b).reverse()
|
||||
const defaultData = params.xAxisData.sort().map((el) => prepareBarTypesData(el, params.xAxisData, params))
|
||||
return {axisData: yAxisDefaultData, updatedData: groupByBarTypesData(defaultData, params.xAxisData, chartsFiltersNames)}
|
||||
}
|
||||
}
|
||||
|
||||
export {isEqualKeyValue, getTextWidth, updatedGroupByData, choiceTypeBarTypesCharts}
|
||||
906
live_monitor_vue/src/store/hooks/Echarts/index.js
Normal file
906
live_monitor_vue/src/store/hooks/Echarts/index.js
Normal file
@@ -0,0 +1,906 @@
|
||||
import * as echarts from "echarts"
|
||||
import {
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent
|
||||
} from 'echarts/components';
|
||||
import { PieChart } from 'echarts/charts';
|
||||
import { LabelLayout } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
|
||||
import {colorList, colorListBar, orderCharts, title, openMachine, groupByKeys, typesCharts} from './staticData'
|
||||
import {viewGraphicsPieBar, viewGraphicsTypesBar} from "./graphicElements";
|
||||
import {updatedGroupByData, choiceTypeBarTypesCharts} from './helpers'
|
||||
import {axisBarConfig, windowConfig, sizeOptions} from './windowConfigs'
|
||||
|
||||
echarts.use([
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
PieChart,
|
||||
CanvasRenderer,
|
||||
LabelLayout
|
||||
])
|
||||
|
||||
const lineConfig = (params, id, windowConfig) => {
|
||||
const times = params.xAxisData
|
||||
const states = params.yAxisData
|
||||
|
||||
const series = [
|
||||
{
|
||||
data: states,
|
||||
type: 'line'
|
||||
}
|
||||
]
|
||||
|
||||
const axisConfig = axisBarConfig(params.type, times, windowConfig, params.names)
|
||||
|
||||
return {
|
||||
title: params.title,
|
||||
titleLeft: 'center',
|
||||
grid: {
|
||||
left: '5%',
|
||||
width: '90%',
|
||||
top: 30,
|
||||
containLabel: true
|
||||
},
|
||||
axisConfig,
|
||||
series,
|
||||
windowConfig,
|
||||
graphic: [{id: 'yAxisTitle', type: 'text', left: 40, top: 20, z: 999, style: {text: '%', fontSize: 12}}]
|
||||
}
|
||||
}
|
||||
|
||||
const linesConfig = (params, id, windowConfig) => {
|
||||
// console.log('params', params)
|
||||
const times = params.xAxisData
|
||||
const text = params.names.yAxis ? params.names.yAxis : 'Гб'
|
||||
const namesLegend = params.charts.map((el) => el.name)
|
||||
|
||||
const series = params.charts.map((el) => {
|
||||
return {
|
||||
name: el.name,
|
||||
data: el.value.map((item) => item.state).reverse(),
|
||||
type: 'line'
|
||||
}
|
||||
})
|
||||
|
||||
const axisConfig = axisBarConfig(params.type, times, windowConfig, params.names)
|
||||
|
||||
return {
|
||||
title: params.title,
|
||||
titleLeft: 'center',
|
||||
legend: {
|
||||
textStyle: {
|
||||
fontSize: windowConfig?.line?.legend?.fontSize
|
||||
},
|
||||
orient: 'horizontal',
|
||||
// top: 360,
|
||||
bottom: windowConfig?.line?.legend?.bottom,
|
||||
// padding: windowConfig?.bar?.legend?.padding,
|
||||
left: 'center',
|
||||
itemGap: windowConfig?.line?.legend?.itemGap,
|
||||
itemWidth: windowConfig?.line?.legend?.itemWidth,
|
||||
itemHeight: windowConfig?.line?.legend?.itemHeight,
|
||||
data: namesLegend
|
||||
},
|
||||
grid: {
|
||||
left: '5%',
|
||||
width: '90%',
|
||||
top: 30,
|
||||
containLabel: true
|
||||
},
|
||||
axisConfig,
|
||||
series,
|
||||
windowConfig,
|
||||
graphic: [{id: 'yAxisTitle', type: 'text', left: 40, top: 20, z: 999, style: {text: text, fontSize: 12}}]
|
||||
}
|
||||
}
|
||||
|
||||
const linesPackConfig = (params, id, windowConfig) => {
|
||||
console.log('params', params)
|
||||
const times = params.xAxisData
|
||||
const text = params.names.yAxis ? params.names.yAxis : ' '
|
||||
const namesLegend = params.charts.map((el) => el.name)
|
||||
|
||||
const series = params.charts.map((el) => {
|
||||
return {
|
||||
name: el.name,
|
||||
data: el.value.map((item) => item.state).reverse(),
|
||||
type: 'line'
|
||||
}
|
||||
})
|
||||
|
||||
const axisConfig = axisBarConfig(params.type, times, windowConfig, params.names)
|
||||
|
||||
return {
|
||||
title: params.title,
|
||||
titleLeft: 'center',
|
||||
legend: {
|
||||
textStyle: {
|
||||
fontSize: windowConfig?.line?.legend?.fontSize
|
||||
},
|
||||
orient: 'horizontal',
|
||||
// top: 360,
|
||||
bottom: windowConfig?.linesPack?.legend?.bottom,
|
||||
// padding: windowConfig?.bar?.legend?.padding,
|
||||
left: 'center',
|
||||
itemGap: windowConfig?.linesPack?.legend?.itemGap,
|
||||
itemWidth: windowConfig?.linesPack?.legend?.itemWidth,
|
||||
itemHeight: windowConfig?.linesPack?.legend?.itemHeight,
|
||||
data: namesLegend
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
width: '95%',
|
||||
height: '50%',
|
||||
top: 30,
|
||||
containLabel: true
|
||||
},
|
||||
axisConfig,
|
||||
series,
|
||||
windowConfig,
|
||||
graphic: [{id: 'yAxisTitle', type: 'text', left: 40, top: 20, z: 999, style: {text: text, fontSize: 12}}]
|
||||
}
|
||||
}
|
||||
|
||||
const pieConfig = (params, id, windowConfig) => {
|
||||
const data = params.machines.map((el, idx) => {
|
||||
el.color = colorList(el.name)
|
||||
el.order = orderCharts(el.name)
|
||||
return el
|
||||
}).sort((a, b) => a.order - b.order)
|
||||
|
||||
const series = [{
|
||||
name: '',
|
||||
type: 'pie',
|
||||
label: {
|
||||
position: 'inner',
|
||||
formatter: (d) => {
|
||||
// console.log('d', d)
|
||||
return d.value
|
||||
},
|
||||
color: '#FFF',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
minAngle: 15,
|
||||
center: ['50%', '50%'],
|
||||
radius: ['40%', '70%'],
|
||||
color: data.map((el) => el.color), //26.12.2023
|
||||
itemStyle: {
|
||||
borderRadius: 5,
|
||||
borderColor: '#FFF',
|
||||
borderWidth: 2
|
||||
},
|
||||
data: data,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
// color: {
|
||||
// type: 'radial',
|
||||
// x: 0.4,
|
||||
// y: 0.3,
|
||||
// r: 1,
|
||||
// // colorStops: [
|
||||
// // {
|
||||
// // offset: 0,
|
||||
// // // color: `rgb(251, 118, 123, 0.5)`,
|
||||
// // opacity: 0.5
|
||||
// // },
|
||||
// // {
|
||||
// // offset: 1,
|
||||
// // // color: `rgb(204, 46, 72, 0.5)`
|
||||
// // opacity: 0.5
|
||||
// // }
|
||||
// // ]
|
||||
// },
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
opacity: 0.7
|
||||
}
|
||||
},
|
||||
}]
|
||||
|
||||
return {
|
||||
title: title(id, params.countMachines),
|
||||
type: 'pie',
|
||||
legend: {
|
||||
formatter: (title) => {
|
||||
const [elem] = params.machines.filter((el) => el.name == title) //26.12.2023
|
||||
return title + ' ' + elem.value
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: windowConfig?.pie?.legend?.fontSize
|
||||
},
|
||||
icon: 'circle',
|
||||
orient: 'horizontal',
|
||||
bottom: 0,
|
||||
left: 'center',
|
||||
itemGap: windowConfig?.pie?.legend?.itemGap,
|
||||
itemWidth: windowConfig?.pie?.legend?.itemWidth,
|
||||
itemHeight: windowConfig?.pie?.legend?.itemHeight,
|
||||
},
|
||||
graphic: [{elements: viewGraphicsPieBar(id, params.period, params.countMachines, title(id, params.countMachines), windowConfig, params.isOpenMenu, params.type)}],
|
||||
series,
|
||||
windowConfig
|
||||
}
|
||||
}
|
||||
|
||||
const barConfig = (params, id, windowConfig) => {
|
||||
// console.log('params', params)
|
||||
const countMachinesWithoutMl = params?.machines?.[params?.machines?.length - 1]?.key === 'routes_without_ml' ? params?.machines?.[params?.machines?.length - 1]?.values : 0
|
||||
|
||||
const data = params?.machines?.map((el, idx) => {
|
||||
el.itemStyle = {color: colorListBar(params.type, el.name)}
|
||||
return el
|
||||
}).sort((a, b) => a?.value - b?.value).reverse()
|
||||
|
||||
const xAxisData = data.map((el) => {
|
||||
return `${el.name}`
|
||||
})
|
||||
|
||||
const series = data?.map((chart, idx) => {
|
||||
return {
|
||||
name: `${chart?.name} ${chart?.value}`,
|
||||
type: 'bar',
|
||||
tooltip: {
|
||||
formatter: value => `${value.marker} ${value.data.name !== null ? value.data.name : 'Нет данных'} ${value.data.value}`
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
overflow: 'break',
|
||||
color: '#FFF',
|
||||
fontWeight: 'bold',
|
||||
textborderColor: '#ff0000'
|
||||
// formatter: '{c}'
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
// color: 'source',
|
||||
color: 'black',
|
||||
}
|
||||
},
|
||||
labelLayout:
|
||||
(p) => {
|
||||
if(p.labelRect.width >= p.rect.width) {
|
||||
return {hideOverlap: true}
|
||||
}
|
||||
},
|
||||
scaleLimit: {
|
||||
min: 1,
|
||||
max: 5
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: chart?.itemStyle?.color,
|
||||
color: chart?.itemStyle?.color,
|
||||
// borderRadius: 5,
|
||||
// borderWidth: 1
|
||||
},
|
||||
barMinWidth: 25,
|
||||
barMinHeight: 20,
|
||||
barMaxWidth: '90%',
|
||||
barGap: '10%',
|
||||
barCategoryGap: '30%',
|
||||
groupPadding: 2,
|
||||
data: data.map((el, index) => idx === index ? el : ''),
|
||||
// data: data, // Для мульти данных, то есть больше чем один тип данных в столбце
|
||||
stack: 'total',
|
||||
sampling: 'average',
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 5,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const axisConfig = axisBarConfig(params.type, xAxisData, windowConfig)
|
||||
|
||||
return {
|
||||
// label: {
|
||||
// show: true,
|
||||
// position: 'inside'
|
||||
// },
|
||||
title: title(id, params.countMachines, params.countMachines_with_ml, params.countMachines_without_ml ? params.countMachines_without_ml : countMachinesWithoutMl),
|
||||
backgroundColor: '#FFF',
|
||||
legend: {
|
||||
textStyle: {
|
||||
fontSize: windowConfig?.bar?.legend?.fontSize
|
||||
},
|
||||
orient: 'horizontal',
|
||||
// top: 360,
|
||||
bottom: windowConfig?.bar?.legend?.bottom,
|
||||
// padding: windowConfig?.bar?.legend?.padding,
|
||||
left: 'center',
|
||||
itemGap: windowConfig?.bar?.legend?.itemGap,
|
||||
itemWidth: windowConfig?.bar?.legend?.itemWidth,
|
||||
itemHeight: windowConfig?.bar?.legend?.itemHeight,
|
||||
},
|
||||
grid: {
|
||||
left: '5%',
|
||||
width: '90%',
|
||||
top: 50,
|
||||
containLabel: true
|
||||
},
|
||||
axisConfig,
|
||||
graphic: [{elements: viewGraphicsPieBar(id, params.period, params.countMachines, title(id, params.countMachinesWithMl, countMachinesWithoutMl), windowConfig, params.isOpenMenu, params.type)}],
|
||||
series,
|
||||
windowConfig
|
||||
}
|
||||
}
|
||||
|
||||
const barTypesConfig = (params, id, windowConfig, enableDataZoom) => {
|
||||
console.log('params', params)
|
||||
const isMlType = params.type === typesCharts.typesMl
|
||||
const isMountType = params.type === 'mount'
|
||||
|
||||
const dataWithoutZero = choiceTypeBarTypesCharts(params).updatedData.dataGroupBy.map((el) => {
|
||||
const result = el.map((item) => {
|
||||
if(item.value === 0) {
|
||||
item.value = ''
|
||||
return item
|
||||
}
|
||||
if(item.name === null) {
|
||||
item.name = 'Нет данных'
|
||||
return item
|
||||
}
|
||||
return item
|
||||
}).sort((a, b) => {
|
||||
if(a.groupByKey && b.groupByKey) {
|
||||
(a.groupByData[a.groupByKey] - b.groupByData[b.groupByKey])
|
||||
}})
|
||||
|
||||
return !isMountType ? result.reverse() : result
|
||||
})
|
||||
|
||||
// console.log('dataWithoutZero', dataWithoutZero)
|
||||
const series = dataWithoutZero.map((charts, idx) => {
|
||||
const [title] = choiceTypeBarTypesCharts(params).updatedData.dataCustomTitle.flat().filter((el) => el?.name == charts[0]?.name)
|
||||
return {
|
||||
name: `${title?.name} ${title?.count} id:${id}`,
|
||||
type: 'bar',
|
||||
tooltip: {
|
||||
formatter: value => `${value.marker} ${value?.data?.name !== null ? value?.data?.name : 'Нет данных'} ${value?.data?.value}`
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
overflow: 'break',
|
||||
color: '#FFF',
|
||||
fontWeight: 'bold',
|
||||
textborderColor: '#ff0000'
|
||||
// formatter: '{c}'
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
// color: 'source',
|
||||
color: 'black',
|
||||
}
|
||||
},
|
||||
labelLayout: (p) => p.labelRect.width <= p.rect.width ? {hideOverlap: true} : {hideOverlap: false},
|
||||
// return {x: initCharts.getWidth() - 50, moveOverlap: 'shiftY'}
|
||||
// return {dx: 50, dy: -100}
|
||||
scaleLimit: {
|
||||
min: 1,
|
||||
max: 5
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: isMlType ? colorListBar(params.type, charts[0]?.name) : colorList(charts[0]?.name),
|
||||
color: isMlType ? colorListBar(params.type, charts[0]?.name) : colorList(charts[0]?.name),
|
||||
// borderRadius: 5,
|
||||
// borderWidth: 1
|
||||
},
|
||||
// barWidth: 30,
|
||||
// barMinWidth: 25,
|
||||
barMaxWidth: 35,
|
||||
barMinHeight: 30,
|
||||
barGap: '20%',
|
||||
barCategoryGap: '10%',
|
||||
groupPadding: 2,
|
||||
// data: data.map((el, index) => idx === index ? el : ''), // Для одного типа данных в столбце
|
||||
data: charts,
|
||||
stack: 'total',
|
||||
sampling: 'average',
|
||||
large: true,
|
||||
largeThreshold: 100,
|
||||
progressiveThreshold: 1000,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 5,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const axisConfig = axisBarConfig(params.type, choiceTypeBarTypesCharts(params).axisData, windowConfig)
|
||||
|
||||
return {
|
||||
// label: {
|
||||
// show: true,
|
||||
// position: 'inside'
|
||||
// },
|
||||
title: isMountType? title(id, params.countMounts) : title(id, params.countMachines, params.countMachinesWithMl, params.countMachinesWithoutMl),
|
||||
legend: {
|
||||
// icon: 'path://M224 320c8.823 0 16 7.177 16 16s-7.177 16-16 16-16-7.177-16-16 7.177-16 16-16m0-32c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zM320 0H128C64 0 0 42.981 0 96v256c0 47.169 50.656 86.391 106.9 94.473l-55.285 55.285c-3.78 3.78-1.103 10.243 4.243 10.243h25.798c3.183 0 6.235-1.264 8.485-3.515L150.627 448h146.745l60.486 60.485a12.002 12.002 0 0 0 8.485 3.515h25.798c5.345 0 8.022-6.463 4.243-10.243L341.1 446.472C397.344 438.391 448 399.169 448 352V96c0-53.019-63-96-128-96zM32 128h384v96H32v-96zm96-96h192c58.237 0 96 37.881 96 64H32c0-32.299 47.552-64 96-64zm192 384H128c-48.448 0-96-31.701-96-64v-96h384v96c0 32.299-47.552 64-96 64z',
|
||||
// backgroundColor: '#ff0000',
|
||||
orient: 'horizontal',
|
||||
textStyle: {
|
||||
fontSize: windowConfig?.bar?.legend?.fontSize
|
||||
},
|
||||
zLevel: 1,
|
||||
// top: 35,
|
||||
left: 'center',
|
||||
bottom: windowConfig?.bar?.legend?.bottom,
|
||||
itemGap: windowConfig?.bar?.legend?.itemGap,
|
||||
itemWidth: windowConfig?.bar?.legend?.itemWidth,
|
||||
itemHeight: windowConfig?.bar?.legend?.itemHeight,
|
||||
formatter: (e) => {
|
||||
return e.slice(0, e.indexOf('id') - 1)
|
||||
}
|
||||
// data: legendData
|
||||
},
|
||||
grid: {
|
||||
left: '1%',
|
||||
top: windowConfig?.bar?.grid?.top,
|
||||
width: windowConfig?.bar?.grid?.width,
|
||||
containLabel: true,
|
||||
height: isMountType ? '68%' : 'auto'
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'slider',
|
||||
show: true,
|
||||
width: 14,
|
||||
top: windowConfig?.bar?.dataZoom?.top,
|
||||
right: '3%',
|
||||
yAxisIndex: 0,
|
||||
realtime: true,
|
||||
start: windowConfig?.bar?.dataZoom?.start,
|
||||
end: 100,
|
||||
rangeMode: ['value', 'percent'],
|
||||
zoomLock: true,
|
||||
brushSelect: false,
|
||||
minSpan: windowConfig?.bar?.dataZoom?.minSpan,
|
||||
handleSize: 18,
|
||||
showDetail: false,
|
||||
handleIcon: "path://M12 0c-9.432 0-12 2.568-12 12s2.551 12 12 12 12-2.551 12-12-2.568-12-12-12z",
|
||||
handleStyle: {
|
||||
color: '#CCCCCC',
|
||||
borderColor: '#CCCCCC'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'inside',
|
||||
yAxisIndex: 0,
|
||||
start: 70,
|
||||
end: 100,
|
||||
zoomOnMouseWheel: false,
|
||||
moveOnMouseWheel: true,
|
||||
handleSize: 18
|
||||
},
|
||||
],
|
||||
graphic: isMountType ? [{elements: viewGraphicsPieBar(id, '', params.countMounts, title(id), windowConfig, params.isOpenMenu, params.type)}] :
|
||||
[{elements: viewGraphicsTypesBar(id, title(id, isMlType ? params.countMachinesWithMl : params.countMachines, params.countMachinesWithoutMl), params.groupByKey, params.prevGroupByData, params.filterKey, params.prevGroupByFilters, params.type, params.groupByValue ? params.groupByValue : params.typeId ? params.typeId : null, params.period, params.yAxisData.length, windowConfig, params.isOpenMenu)}],
|
||||
series,
|
||||
axisConfig,
|
||||
windowConfig,
|
||||
enableDataZoom
|
||||
}
|
||||
}
|
||||
|
||||
const config = (params) => {
|
||||
// console.log('params', params)
|
||||
return {
|
||||
title: {
|
||||
text: params.title,
|
||||
subtextStyle: {
|
||||
fontSize: 11,
|
||||
align: 'left'
|
||||
},
|
||||
top: params.windowConfig?.title?.top,
|
||||
left: params.titleLeft ? params.titleLeft : params.windowConfig?.title?.left,
|
||||
textStyle: {
|
||||
width: params.windowConfig?.title?.width,
|
||||
fontSize: params.windowConfig?.title?.fontSize,
|
||||
overflow: 'break',
|
||||
},
|
||||
|
||||
},
|
||||
backgroundColor: params.windowConfig?.backgroundColor,
|
||||
tooltip: {trigger: 'item'},
|
||||
legend: params.legend,
|
||||
xAxis: params.axisConfig?.xAxis ? params.axisConfig?.xAxis : '',
|
||||
yAxis: params.axisConfig?.yAxis ? params.axisConfig?.yAxis : '',
|
||||
dataZoom: params.enableDataZoom && params.dataZoom ? params.dataZoom : '',
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
mark: {show: true},
|
||||
// dataView: {show: true, readOnly: false, title: ''},
|
||||
// magicType: {show: true, type: ['line', 'bar'], title: {line: 'Переключить на линейный график', bar: 'Переключить на график гистограмму'} },
|
||||
// restore: {show: true, title: 'Обновить'},
|
||||
// saveAsImage: {show: true, title: ''}
|
||||
},
|
||||
iconStyle: {
|
||||
borderColor: '#333',
|
||||
// borderWidth: 0,
|
||||
// borderType: 'solid'
|
||||
},
|
||||
emphasis: {iconStyle: {
|
||||
borderColor: 'rgba(1, 97, 253, 0.3)',
|
||||
|
||||
}},
|
||||
top: 16,
|
||||
right: 20,
|
||||
},
|
||||
graphic: params.graphic ? params.graphic : '',
|
||||
grid: params.grid ? params.grid : '',
|
||||
series: params.series
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ECharts {
|
||||
constructor(id, sizes = {}) {
|
||||
this.id = id
|
||||
this.el = document.getElementById(id)
|
||||
const clientWidth = window.innerWidth
|
||||
const clientHeight = window.innerHeight
|
||||
this.isMobile = clientWidth < 768
|
||||
this.currentWidth = clientWidth - 100
|
||||
// const currentheight = clientHeight + 70
|
||||
this.widthChart = sizes.width ? sizes.width : 450
|
||||
this.heightChart = sizes.height ? sizes.height : 350
|
||||
this.mountHeightChart = 350
|
||||
this.charts = []
|
||||
this.legendFilterData = []
|
||||
|
||||
this.initCharts = echarts.init(this.el, null, {renderer: 'canvas'})
|
||||
this.initCharts.showLoading()
|
||||
|
||||
// window.addEventListener('resize', function() {
|
||||
// this.initCharts.resize()
|
||||
// })
|
||||
console.log('sizes', sizes)
|
||||
this.getRes = this.el.getAttribute("get-res") == "true"
|
||||
|
||||
}
|
||||
|
||||
getChart(id) {
|
||||
if (this.getRes) {
|
||||
return {isGetChart: true, id}
|
||||
}
|
||||
}
|
||||
|
||||
makeLineCharts(charts) {
|
||||
charts.chartId = this.id
|
||||
this.charts = []
|
||||
this.charts.push(charts)
|
||||
this.initCharts.resize(sizeOptions(this.isMobile ? this.currentWidth : this.widthChart, charts.height ? charts.height : this.heightChart))
|
||||
this.initCharts.setOption(config(lineConfig(charts, this.id, windowConfig(this.isMobile, this.currentWidth))), {
|
||||
replaceMerge: ["series", "yAxis", "xAxis", "grid"],
|
||||
})
|
||||
this.initCharts.hideLoading()
|
||||
}
|
||||
|
||||
makeLinesCharts(charts) {
|
||||
console.log('charts', charts)
|
||||
charts.chartId = this.id,
|
||||
this.charts = []
|
||||
this.charts.push(charts)
|
||||
if(this.initCharts) {
|
||||
this.initCharts.resize(sizeOptions(this.isMobile ? this.currentWidth : this.widthChart, charts.height ? charts.height : this.heightChart))
|
||||
this.initCharts.setOption(config(linesConfig(this.charts[0], this.id, windowConfig(this.isMobile, this.currentWidth))), {
|
||||
replaceMerge: ["series", "yAxis", "xAxis", "grid"],
|
||||
})
|
||||
}
|
||||
this.initCharts.hideLoading()
|
||||
}
|
||||
|
||||
makeLinesPackCharts(charts) {
|
||||
console.log('charts', charts)
|
||||
charts.chartId = this.id,
|
||||
this.charts = []
|
||||
this.charts.push(charts)
|
||||
if(this.initCharts) {
|
||||
// this.initCharts.resize(sizeOptions(this.isMobile ? this.currentWidth : this.widthChart, charts.height ? charts.height : this.heightChart))
|
||||
this.initCharts.setOption(config(linesPackConfig(this.charts[0], this.id, windowConfig(this.isMobile, this.currentWidth))), {
|
||||
replaceMerge: ["series", "yAxis", "xAxis", "grid"],
|
||||
})
|
||||
}
|
||||
this.initCharts.hideLoading()
|
||||
}
|
||||
|
||||
updateLineCharts(charts) {
|
||||
if(this.charts[0].xAxisData[this.charts[0].xAxisData.length - 1] !== charts.xAxisData[charts.xAxisData.length - 1]) {
|
||||
this.charts[0].xAxisData.shift()
|
||||
this.charts[0].xAxisData.push(charts.xAxisData[charts.xAxisData.length - 1])
|
||||
this.charts[0].yAxisData.shift()
|
||||
this.charts[0].yAxisData.push(charts.yAxisData[charts.yAxisData.length - 1])
|
||||
if(this.initCharts) {
|
||||
this.initCharts.setOption({xAxis: [{data: this.charts[0].xAxisData}], series: [{data: this.charts[0].yAxisData}]}, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateLinesCharts(charts) {
|
||||
// console.log('charts', charts)
|
||||
if(this.charts[0].xAxisData[this.charts[0].xAxisData.length - 1] !== charts.xAxisData[charts.xAxisData.length - 1]) {
|
||||
this.charts[0].charts.forEach((el, idx) => {
|
||||
el.value.pop()
|
||||
el.value.unshift(charts.charts[idx].value[0])
|
||||
})
|
||||
this.charts[0].xAxisData.shift()
|
||||
this.charts[0].xAxisData.push(charts.xAxisData[charts.xAxisData.length - 1])
|
||||
if(this.initCharts) {
|
||||
this.initCharts.setOption({xAxis: [{data: this.charts[0].xAxisData}],series: [{data: this.charts[0].charts}]}, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makePieCharts(charts) {
|
||||
charts.chartId = this.id
|
||||
this.charts = []
|
||||
this.charts.push(charts)
|
||||
const initCharts = echarts.init(this.el, null, sizeOptions(this.widthChart, this.heightChart), {renderer: 'canvas'})
|
||||
initCharts.hideLoading()
|
||||
initCharts.resize(sizeOptions(this.isMobile ? this.currentWidth : this.widthChart, this.heightChart))
|
||||
initCharts.setOption(config(pieConfig(charts, this.id, windowConfig(this.isMobile, this.currentWidth))), true)
|
||||
}
|
||||
|
||||
makeBarCharts(charts) {
|
||||
charts.chartId = this.el.id
|
||||
this.charts = []
|
||||
this.charts.push(charts)
|
||||
const initCharts = echarts.init(document.getElementById(`${this.el.id}`), null, {renderer: 'canvas'})
|
||||
initCharts.hideLoading()
|
||||
initCharts.resize(sizeOptions(this.isMobile ? this.currentWidth : this.widthChart, this.heightChart))
|
||||
initCharts.setOption(config(barConfig(charts, this.el.id, windowConfig(this.isMobile, this.currentWidth))), true)
|
||||
}
|
||||
|
||||
makeBarMountCharts(charts) {
|
||||
// console.log('charts', charts)
|
||||
charts.chartId = this.el.id
|
||||
this.charts = []
|
||||
this.charts.push(charts)
|
||||
this.mountHeightChart = charts.height
|
||||
const initCharts = echarts.init(document.getElementById(`${this.el.id}`), null, {renderer: 'canvas'})
|
||||
initCharts.hideLoading()
|
||||
initCharts.resize(sizeOptions(this.isMobile ? this.currentWidth : this.widthChart, charts.height))
|
||||
initCharts.setOption(config(barTypesConfig(charts, this.el.id, windowConfig(this.isMobile, this.currentWidth))), true)
|
||||
}
|
||||
|
||||
makeBarTypesCharts(charts) {
|
||||
charts.chartId = this.el.id
|
||||
this.charts = []
|
||||
this.charts.push(charts)
|
||||
console.log('charts - makeBarTypesCharts', charts.legendFilterData)
|
||||
const initCharts = echarts.init(document.getElementById(`${this.el.id}`), null, {renderer: 'canvas'})
|
||||
// this.heightChart = 270 + (charts?.yAxisData?.length * 27)
|
||||
const countCharts = charts?.yAxisData?.length
|
||||
const enableDataZoom = (countCharts * 35) + 200 > this.heightChart
|
||||
initCharts.hideLoading()
|
||||
initCharts.resize(sizeOptions(this.isMobile ? this.currentWidth : this.widthChart, this.heightChart))
|
||||
initCharts.setOption(config(barTypesConfig(charts, this.el.id, windowConfig(this.isMobile, this.currentWidth, countCharts), enableDataZoom)), true)
|
||||
|
||||
let currentLegendCount = 0
|
||||
if(charts.legendFilterData?.length > 0) {
|
||||
charts.legendFilterData?.map((legend) => {
|
||||
if(legend.id === this.el.id) {
|
||||
if(this.el.id !== 'routes_types_bar_ml') {
|
||||
charts.xAxisData.map((chart) => {
|
||||
chart.charts.reduce((acc, el) => {
|
||||
if(el.name === legend.filterData.name) {
|
||||
currentLegendCount += Number(el.value)
|
||||
} else {
|
||||
return acc
|
||||
}
|
||||
}, 0)
|
||||
})
|
||||
} else {
|
||||
charts.xAxisData.map((chart) => {
|
||||
chart.reduce((acc, el) => {
|
||||
if(el.charts.name === legend.filterData.name) {
|
||||
currentLegendCount += Number(el.charts.value)
|
||||
} else {
|
||||
return acc
|
||||
}
|
||||
}, 0)
|
||||
})
|
||||
}
|
||||
|
||||
initCharts.dispatchAction({
|
||||
type: !legend.filterData.value ? 'legendUnSelect' : 'legendSelect',
|
||||
name: `${legend.filterData.name} ${currentLegendCount} id:${legend.id}`
|
||||
})
|
||||
}
|
||||
currentLegendCount = 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setMenuChart (data) {
|
||||
if (data.groupByInfo.chartId) {
|
||||
this.charts = [data.groupByInfo.data]
|
||||
const [chart] = this.charts
|
||||
chart.chartId = data.groupByInfo.chartId
|
||||
chart.type = data.groupByInfo.type
|
||||
chart.isOpenMenu = data.openMenuChart
|
||||
const initCharts = echarts.init(document.getElementById(`${data.groupByInfo.chartId}`), null, {renderer: 'canvas'})
|
||||
const countCharts = chart?.yAxisData?.length
|
||||
const enableDataZoom = (chart?.yAxisData?.length * 35) + 200 > this.heightChart
|
||||
initCharts.hideLoading()
|
||||
initCharts.resize(sizeOptions(this.isMobile ? this.currentWidth : this.widthChart, data.groupByInfo.chartId.includes('mount') ? chart.height : this.heightChart))
|
||||
if(data.groupByInfo.chartId.includes('pie')) {
|
||||
initCharts.setOption(config(pieConfig(chart, data.groupByInfo.chartId, windowConfig(this.isMobile, this.currentWidth))), true)
|
||||
} else if (data.groupByInfo.chartId.includes('types_bar')) {
|
||||
initCharts.setOption(config(barTypesConfig(chart, data.groupByInfo.chartId, windowConfig(this.isMobile, this.currentWidth, countCharts), enableDataZoom)), true)
|
||||
} else if (data.groupByInfo.chartId.includes('mount')) {
|
||||
initCharts.setOption(config(barTypesConfig(chart, data.groupByInfo.chartId, windowConfig(this.isMobile, this.currentWidth))), true)
|
||||
} else {
|
||||
initCharts.setOption(config(barConfig(chart, data.groupByInfo.chartId, windowConfig(this.isMobile, this.currentWidth))), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloadChart (data, updatedData) {
|
||||
if (data.groupByInfo.chartId) {
|
||||
this.charts = [data.groupByInfo.data]
|
||||
const [chart] = this.charts
|
||||
const countCharts = chart?.yAxisData?.length
|
||||
const initCharts = echarts.init(document.getElementById(`${data.groupByInfo.chartId}`), null, {renderer: 'canvas'})
|
||||
const enableDataZoom = (countCharts * 35) + 200 > data.updatedHeight
|
||||
initCharts.hideLoading()
|
||||
|
||||
if(data.groupByInfo.chartId.includes('pie')) {
|
||||
initCharts.setOption(config(pieConfig(chart, data.groupByInfo.chartId, windowConfig(this.isMobile, this.currentWidth))), true)
|
||||
this.downloadImage(data.groupByInfo.title, this.prepareImage(initCharts))
|
||||
} else if (data.groupByInfo.chartId.includes('types_bar')) {
|
||||
console.log('this.charts', this.charts)
|
||||
initCharts.resize(sizeOptions(this.isMobile ? this.currentWidth : this.widthChart, data.groupByInfo.updatedHeight))
|
||||
initCharts.setOption(config(barTypesConfig(chart, data.groupByInfo.chartId, windowConfig(this.isMobile, data.groupByInfo.updatedHeight, countCharts), enableDataZoom)), true)
|
||||
|
||||
initCharts.on('finished', () => {
|
||||
if(data.groupByInfo.isDownloadChart) {
|
||||
console.log('data', data)
|
||||
this.downloadImage(data.groupByInfo.title, this.prepareImage(initCharts))
|
||||
data.groupByInfo.isDownloadChart = false
|
||||
updatedData({action: 'downloadChart', groupByInfo: {...data.groupByInfo, updatedHeight: this.heightChart, data: this.charts}})
|
||||
}
|
||||
})
|
||||
|
||||
} else if (data.groupByInfo.chartId.includes('mount')) {
|
||||
initCharts.setOption(config(barTypesConfig(chart, data.groupByInfo.chartId, windowConfig(this.isMobile, this.currentWidth))), true)
|
||||
this.downloadImage(data.groupByInfo.title, this.prepareImage(initCharts))
|
||||
} else {
|
||||
initCharts.setOption(config(barConfig(chart, data.groupByInfo.chartId, windowConfig(this.isMobile, this.currentWidth))), true)
|
||||
console.log('data.groupByInfo.title', data.groupByInfo.title)
|
||||
this.downloadImage(data.groupByInfo.title, this.prepareImage(initCharts))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareImage(initCharts) {
|
||||
const img = new Image()
|
||||
img.src = initCharts.getDataURL({type: 'canvas'})
|
||||
const myHeaders = new Headers()
|
||||
const myRequest = new Request(img.src, {
|
||||
method: "GET",
|
||||
headers: myHeaders,
|
||||
mode: "cors",
|
||||
cache: "default",
|
||||
})
|
||||
return myRequest
|
||||
}
|
||||
|
||||
async downloadImage(name, myRequest) {
|
||||
try {
|
||||
const response = await fetch(myRequest)
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not OK")
|
||||
}
|
||||
const myBlob = await response.blob()
|
||||
const imageURL = URL.createObjectURL(myBlob)
|
||||
const link = document.createElement('a')
|
||||
link.href = imageURL
|
||||
if (link.href) {
|
||||
const openedImage = window.open(link.href, '_blank')
|
||||
console.log('openedImage', openedImage)
|
||||
const linkImg = openedImage.document.createElement('a')
|
||||
linkImg.href = link.href
|
||||
linkImg.download = name
|
||||
openedImage.document.body.appendChild(linkImg)
|
||||
linkImg.click()
|
||||
openedImage.document.body.removeChild(linkImg)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error:", error)
|
||||
}
|
||||
}
|
||||
|
||||
updateLegendFiltersData(params) {
|
||||
const [chart] = this.charts
|
||||
console.log('params - updateLegendFilterData', params)
|
||||
console.log('chart - updateLegendFilterData', chart)
|
||||
if (params.id === chart.chartId) {
|
||||
if(!params.filterData.value) {
|
||||
this.legendFilterData.push(params)
|
||||
return this.legendFilterData
|
||||
} else {
|
||||
const updatedFiltersData = this.legendFilterData.filter((legend) => legend.filterData.name !== params.filterData.name)
|
||||
this.legendFilterData = updatedFiltersData
|
||||
return updatedFiltersData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClickedBarCharts(updatedData) {
|
||||
this.initCharts.on('click', (e) => {
|
||||
// console.log('e', e)
|
||||
const filterKey = e.data?.filterKey ? e.data?.filterKey : e.data?.filterKey
|
||||
if(filterKey && e.componentType === 'series' && e.data?.type !== 'mount') {
|
||||
// console.log('this.charts - nextLayoutChart', this.charts)
|
||||
updatedData({id: this.id, action: 'nextLayoutChart', data: this.charts, prevGroupByData: e.data.groupByData, prevGroupByFilters: e.data.prevGroupByData, filterKey: filterKey, groupByKey: e.data.groupByKey, groupByValue: e.data.groupByValue ? e.data.groupByValue : null, type: e.data.type})
|
||||
}
|
||||
if(e.event?.target?.id && e.info?.typeEl === 'buttonPrevious' && e.componentType === 'graphic') {
|
||||
// console.log('beforeLayoutChart')
|
||||
// console.log('e.info', e.info)
|
||||
updatedData({id: this.el.id, action: 'beforeLayoutChart', filterKey: e.event.target.id, groupByInfo: e.info})
|
||||
}
|
||||
if(e.event?.target?.id && e.info?.typeEl === 'menu' && e.componentType === 'graphic') {
|
||||
e.info.mountHeightChart = this.mountHeightChart
|
||||
// console.log('this.charts - menu', this.charts)
|
||||
updatedData({action: 'openMenuChart', groupByInfo: {...e.info, data: this.charts}})
|
||||
}
|
||||
if(e.event?.target?.id && (e.info?.typeEl === 'download' || e.info?.typeEl === 'downloadTitle') && e.componentType === 'graphic') {
|
||||
const fullHeight = e.info.countCharts * 35 + 200
|
||||
e.info.isDownloadChart = true
|
||||
updatedData({action: 'downloadChart', groupByInfo: {...e.info, updatedHeight: fullHeight, data: this.charts}})
|
||||
}
|
||||
if(e.event?.target?.id && e.info?.typeEl === 'link' && e.componentType === 'graphic') {
|
||||
const id = e.event?.target?.parent?.id
|
||||
const idx = id.slice(0, id.indexOf(':'))
|
||||
const layoutGroupKey = id.slice((id.indexOf('_') + 1), id.indexOf('-')).trim()
|
||||
const layoutGroupValue = id.slice(id.indexOf('-') + (id.includes('/') ? 3 : 2)).trim()
|
||||
const isCurrentLayout = Object.values(e.info?.prevGroupByData)[0] === layoutGroupValue
|
||||
if (isCurrentLayout && Object.keys(e.info.prevGroupByFilters).length > 1) { // Для выхода с первого вложенного слоя группировки
|
||||
return false
|
||||
} else {
|
||||
// console.log('e.info', e.info)
|
||||
const updatedPrevGroupByFilters = updatedGroupByData(idx, e.info.prevGroupByFilters, layoutGroupKey, layoutGroupValue)
|
||||
e.info.prevGroupByFilters = updatedPrevGroupByFilters
|
||||
e.info.prevGroupByData = {[layoutGroupKey]: layoutGroupValue}
|
||||
updatedData({id: this.el.id, action: 'beforeLayoutChart', filterKey: e.info.filterKey ? e.info.filterKey : null, groupByInfo: e.info})
|
||||
}
|
||||
}
|
||||
if(e.data?.groupByKey === groupByKeys.fiveLayout) {
|
||||
openMachine(e.data)
|
||||
}
|
||||
})
|
||||
|
||||
this.initCharts.on('legendselectchanged', (params) => {
|
||||
if(params.name.indexOf('id:') > 0) {
|
||||
// Берём все данные производные из имени, так как это активный элемент легенды
|
||||
const id = params.name.slice((params.name.indexOf('id:') + 3))
|
||||
const nameIdxEnd = (params.name.indexOf('id:') - 1)
|
||||
const namePrepare = params.name.slice(0, nameIdxEnd)
|
||||
const name = namePrepare.slice(0, namePrepare.lastIndexOf(' '))
|
||||
const updatedLegendFiltersData = this.updateLegendFiltersData({id: this.el.id, filterData: {name: name, value: params.selected[params.name]}})
|
||||
updatedData({action: 'legendFiltersParams', data: updatedLegendFiltersData})
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ECharts;
|
||||
118
live_monitor_vue/src/store/hooks/Echarts/staticData.js
Normal file
118
live_monitor_vue/src/store/hooks/Echarts/staticData.js
Normal file
@@ -0,0 +1,118 @@
|
||||
const chartsSetup = {
|
||||
main: [
|
||||
{id: 'commun_pie', type: 'pie', maxHeight: '400px', order: 1},
|
||||
{id: 'commun_pie_cdim', type: 'pie', maxHeight: '400px', order: 2},
|
||||
{id: 'commun_pie_cdrp', type: 'pie', maxHeight: '400px', order: 3},
|
||||
{id: 'commun_pie_mpt_types', type: 'pie', maxHeight: '400px', order: 4},
|
||||
{id: 'commun_pie_gdsm_types', type: 'pie', maxHeight: '400px', order: 5},
|
||||
{id: 'commun_pie_sm_pss_types', type: 'pie', maxHeight: '400px', order: 6},
|
||||
{id: 'routes_pie', type: 'pie', maxHeight: '400px', order: 7},
|
||||
{id: 'rors_pie', type: 'pie', maxHeight: '400px', order: 8},
|
||||
{id: 'routes_bar', type: 'mainMl', maxHeight: '400px', order: 9},
|
||||
{id: 'mount_client_type_bar', type: 'mount', height: '350', maxHeight: '400px', order: 10},
|
||||
{id: 'mount_mounter_type_bar', type: 'mount', height: '500', maxHeight: '600px', order: 11},
|
||||
],
|
||||
types: [
|
||||
{id: 'commun_types_bar', type: 'default', maxHeight: '400px', order: 1},
|
||||
{id: 'commun_types_bar_cdim', type: 'without_first_layout', maxHeight: '400px', order: 2},
|
||||
{id: 'commun_types_bar_cdrp', type: 'without_first_layout', maxHeight: '400px', order: 3},
|
||||
{id: 'commun_types_bar_mpt_types', type: 'types', maxHeight: '400px', order: 4},
|
||||
{id: 'commun_types_bar_gdsm_types', type: 'types', maxHeight: '400px', order: 5},
|
||||
{id: 'commun_types_bar_sm_pss_types', type: 'types', maxHeight: '400px', order: 6},
|
||||
{id: 'routes_types_bar', type: 'default', maxHeight: '400px', order: 7},
|
||||
{id: 'rors_types_bar', type: 'default', maxHeight: '400px', order: 8},
|
||||
{id: 'routes_types_bar_ml', type: 'ml', maxHeight: '380px', order: 9},
|
||||
]
|
||||
}
|
||||
|
||||
const colorList = (name) => {
|
||||
switch(name) {
|
||||
case 'На связи': return 'rgb(134, 212, 131, 1)'
|
||||
case 'Есть МЛ, выходил на связь': return 'rgb(134, 212, 131, 1)'
|
||||
case 'РОРС': return 'rgb(134, 212, 131, 1)'
|
||||
case 'Не на связи': return 'rgb(255, 114, 114, 1)'
|
||||
case 'Нет МЛ, нет связи': return 'rgb(165, 165, 165, 1)'
|
||||
case 'Не РОРС': return 'rgb(255, 114, 114, 1)'
|
||||
case 'Нет данных': return 'rgb(165, 165, 165, 1)'
|
||||
case 'Есть МЛ, нет связи': return 'rgb(255, 114, 114, 1)'
|
||||
case 'Нет МЛ, выходил на связь': return 'rgb(243, 161, 30, 1)'
|
||||
}
|
||||
}
|
||||
|
||||
const colorListBar = (type, name) => {
|
||||
if(type === typesCharts.mainMl || typesCharts.typesMl) {
|
||||
switch(name) {
|
||||
case 'Обычные': return 'rgb(134, 212, 131, 1)'
|
||||
case 'Аварийные': return 'rgb(255, 114, 114, 1)'
|
||||
case 'Прочие': return 'rgb(196, 161, 201, 1)'
|
||||
case 'Ремонтные': return 'rgb(228, 202, 163, 1)'
|
||||
case 'Без выезда': return 'rgb(243, 161, 30, 1)'
|
||||
case 'СПС без МЛ': return 'rgb(165, 165, 165, 1)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const orderCharts = (name) => {
|
||||
switch(name) {
|
||||
case 'На связи': return 1
|
||||
case 'Есть МЛ, выходил на связь': return 1
|
||||
case 'РОРС': return 1
|
||||
case 'Не на связи': return 2
|
||||
case 'Не РОРС': return 2
|
||||
case 'Есть МЛ, нет связи': return 2
|
||||
case 'Нет МЛ, выходил на связь': return 3
|
||||
case 'Нет МЛ, нет связи': return 5
|
||||
case 'Нет данных': return 5
|
||||
}
|
||||
}
|
||||
|
||||
const title = (id, count, countWithoutMl = '') => {
|
||||
switch(id) {
|
||||
case 'commun_pie': return `Связь с СПС`
|
||||
case 'commun_pie_cdim': return `Связь с СПС, ЦДИМ`
|
||||
case 'commun_pie_cdrp': return `Связь с СПС, ЦДРП`
|
||||
case 'commun_types_bar': return `Связь с СПС`
|
||||
case 'commun_types_bar_cdim': return `Связь с СПС, ЦДИМ`
|
||||
case 'commun_types_bar_cdrp': return `Связь с СПС, ЦДРП`
|
||||
case 'commun_pie_mpt_types': return `Наличие связи с МРТ`
|
||||
case 'commun_pie_gdsm_types': return `Наличие связи с ЖДСМ`
|
||||
case 'commun_pie_sm_pss_types': return `Наличие связи с СМ`
|
||||
case 'commun_types_bar_mpt_types': return `Наличие связи с МРТ`
|
||||
case 'commun_types_bar_gdsm_types': return `Наличие связи с ЖДСМ`
|
||||
case 'commun_types_bar_sm_pss_types': return `Наличие связи с СМ`
|
||||
case 'rors_pie': return `РОРС`
|
||||
case 'rors_types_bar': return `РОРС`
|
||||
case 'routes_pie': return `Связь с СПС, МЛ`
|
||||
case 'routes_types_bar': return `Связь с СПС, МЛ`
|
||||
case 'routes_bar': return `Выданные МЛ`
|
||||
case 'routes_types_bar_ml': return `Выданные МЛ`
|
||||
case 'mount_client_type_bar': return `Кол-во монтажей по заказчикам и КР`
|
||||
case 'mount_mounter_type_bar': return `Кол-во монтажей по исполнителям и КР`
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
|
||||
const openMachine = (data) => {
|
||||
const [currentEl] = data?.linkData?.filter((el) => el?.machine_type === data?.groupByData[data?.groupByKey])
|
||||
window.open(`/html/askr_devices/analyze_device/${currentEl.device_number}`, '_blank')
|
||||
}
|
||||
|
||||
const groupByKeys = {
|
||||
firstLayout: 'org_type_name',
|
||||
secondLayout: 'railway_name',
|
||||
thirdLayout: 'org_name',
|
||||
fourthLayout: 'type',
|
||||
fiveLayout: 'machine_type',
|
||||
}
|
||||
|
||||
const typesCharts = {
|
||||
default: 'default',
|
||||
without_first_layout: 'without_first_layout',
|
||||
mpt: 'mpt_types',
|
||||
gdsm: 'gdsm_types',
|
||||
sm: 'sm_pss_types',
|
||||
mainMl: 'mainMl',
|
||||
typesMl: 'ml',
|
||||
}
|
||||
|
||||
export {colorList, colorListBar, orderCharts, title, openMachine, groupByKeys, typesCharts, chartsSetup}
|
||||
381
live_monitor_vue/src/store/hooks/Echarts/windowConfigs.js
Normal file
381
live_monitor_vue/src/store/hooks/Echarts/windowConfigs.js
Normal file
@@ -0,0 +1,381 @@
|
||||
const axisBarConfig = (typeChart, axisData, windowConfig, names = {}) => {
|
||||
return typeChart === 'mount' || typeChart === 'mainMl' || typeChart === 'states' || typeChart === 'packLines' ? {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
name: names.xAxis ? names.xAxis : '',
|
||||
nameLocation: 'end',
|
||||
nameGap: 35,
|
||||
nameTextStyle: {
|
||||
verticalAlign: 'top',
|
||||
align: 'right',
|
||||
padding: typeChart === 'packLines' ? windowConfig?.linesPack.nameXAxis.padding : [25, 40, 0, 0]
|
||||
},
|
||||
splitLine: {show: true},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: 'black',
|
||||
fontSize: windowConfig?.bar?.xAxis?.label?.fontSize,
|
||||
fontWeight: windowConfig?.bar?.xAxis?.label?.fontWeight,
|
||||
interval: windowConfig?.bar?.xAxis?.label?.interval,
|
||||
align: windowConfig?.bar?.xAxis?.label?.align,
|
||||
hideOverlap: true,
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
interval: windowConfig?.bar?.xAxis?.axisTick?.interval,
|
||||
lineStyle: {
|
||||
color: 'black',
|
||||
}
|
||||
},
|
||||
data: axisData
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: names.xAxis ? names.xAxis : '',
|
||||
// nameLocation: 'end',
|
||||
// nameGap: 35,
|
||||
// nameTextStyle: {
|
||||
// // verticalAlign: 'top',
|
||||
// // align: 'right',
|
||||
// padding: [125, 0, 0, 150]
|
||||
// },
|
||||
splitLine: {show: true},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
// dashOffset: 10,
|
||||
width: 1,
|
||||
color: 'white',
|
||||
shadowColor: 'black',
|
||||
shadowOffsetX: -2
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: 'black',
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: 'black'
|
||||
}
|
||||
},
|
||||
},
|
||||
} : {
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: names.xAxis ? names.xAxis : '',
|
||||
splitLine: {show: true},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: 'black',
|
||||
fontSize: windowConfig?.bar?.xAxis?.label?.fontSize,
|
||||
fontWeight: windowConfig?.bar?.xAxis?.label?.fontWeight,
|
||||
interval: windowConfig?.bar?.xAxis?.label?.interval,
|
||||
align: windowConfig?.bar?.xAxis?.label?.align,
|
||||
hideOverlap: true,
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: 'black',
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
interval: windowConfig?.bar?.xAxis?.axisTick?.interval,
|
||||
lineStyle: {
|
||||
color: 'black'
|
||||
}
|
||||
},
|
||||
minInterval: 1,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
name: names.yAxis ? names.yAxis : '',
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
// dashOffset: 10,
|
||||
width: 1,
|
||||
color: 'white',
|
||||
shadowColor: 'black',
|
||||
shadowOffsetX: -2
|
||||
},
|
||||
},
|
||||
// axisLabel: {
|
||||
// formatter: (yAxisData) => {
|
||||
// // return yAxisData.map((el) => {
|
||||
// // return `${el?.name} ${el?.value}`
|
||||
// // })
|
||||
// }
|
||||
// },
|
||||
axisLabel: {
|
||||
show: true,
|
||||
width: windowConfig?.bar?.yAxis?.label?.width,
|
||||
color: 'black',
|
||||
fontSize: windowConfig?.bar?.yAxis?.label?.fontSize,
|
||||
fontWeight: windowConfig?.bar?.yAxis?.label?.fontWeight,
|
||||
overflow: 'break',
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: 'black'
|
||||
}
|
||||
},
|
||||
// fontSize: 10,
|
||||
// offset: -7,
|
||||
data: axisData,
|
||||
splitLine: {
|
||||
show: true
|
||||
},
|
||||
// position: windowConfig?.bar?.yAxis?.label?.position,
|
||||
// offset: windowConfig?.bar?.yAxis?.label?.offset,
|
||||
zLevel: 3,
|
||||
// z: 99
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const windowConfig = (isMobile, currentWidth, countCharts) => {
|
||||
|
||||
const scrollbarParams = (countCharts) => {
|
||||
if(15 < countCharts && countCharts < 40) return {
|
||||
start: 85, minSpan: 5
|
||||
}
|
||||
if(40 < countCharts && countCharts < 70) return {
|
||||
start: 90, minSpan: 5
|
||||
}
|
||||
if(70 < countCharts && countCharts < 100) return {
|
||||
start: 95, minSpan: 5
|
||||
}
|
||||
if(100 < countCharts) return {
|
||||
start: 99, minSpan: 3
|
||||
}
|
||||
return {start: 70, minSpan: 5}
|
||||
|
||||
}
|
||||
|
||||
if(isMobile) return {
|
||||
title: {
|
||||
width: currentWidth - 90,
|
||||
top: 3,
|
||||
left: 'left',
|
||||
fontSize: 11,
|
||||
},
|
||||
backgroundColor: '#FFF',
|
||||
pie: {
|
||||
legend: {
|
||||
itemWidth: 15,
|
||||
itemHeight: 15,
|
||||
itemGap: 5,
|
||||
fontSize: 10
|
||||
},
|
||||
graphic: {
|
||||
period: {
|
||||
top: 10,
|
||||
right: 40,
|
||||
fontSize: 10
|
||||
},
|
||||
count: {
|
||||
top: 'center',
|
||||
left: 'center',
|
||||
fontSize: 20
|
||||
},
|
||||
}
|
||||
},
|
||||
linesPack: {
|
||||
legend: {
|
||||
bottom: 0,
|
||||
itemWidth: 25,
|
||||
itemHeight: 14,
|
||||
itemGap: 7,
|
||||
fontSize: 11
|
||||
},
|
||||
nameXAxis: {
|
||||
padding: [110, 40, 0, 0],
|
||||
}
|
||||
},
|
||||
bar: {
|
||||
legend: {
|
||||
bottom: 0,
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
itemGap: 5,
|
||||
fontSize: 10
|
||||
},
|
||||
grid: {
|
||||
width: '85%',
|
||||
top: 40
|
||||
},
|
||||
yAxis: {
|
||||
label: {width: 40, fontWeight: 'bold', fontSize: 9, position: 'left', offset: -20}
|
||||
},
|
||||
xAxis: {
|
||||
label: {fontSize: 10, fontWeight: 'bold', interval: 1, align: 'center'},
|
||||
axisTick: {interval: 1}
|
||||
},
|
||||
dataZoom: {
|
||||
top: 40,
|
||||
start: scrollbarParams(countCharts).start,
|
||||
minSpan: scrollbarParams(countCharts).minSpan,
|
||||
},
|
||||
graphic: {
|
||||
period: {
|
||||
top: 8,
|
||||
right: 50,
|
||||
fontSize: 10
|
||||
},
|
||||
menu: {
|
||||
top: 5,
|
||||
right: 0
|
||||
},
|
||||
container: {
|
||||
top: 27,
|
||||
right: 0
|
||||
},
|
||||
rect: {
|
||||
top: 8,
|
||||
right: 10
|
||||
},
|
||||
download: {
|
||||
top: 18,
|
||||
right: 70
|
||||
},
|
||||
downloadTitle: {
|
||||
top: 23,
|
||||
right: 20
|
||||
},
|
||||
back: {
|
||||
top: 5,
|
||||
right: 20,
|
||||
},
|
||||
link: {
|
||||
top: 23,
|
||||
left: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!isMobile) return {
|
||||
title: {
|
||||
width: 'auto',
|
||||
top: 3,
|
||||
left: 'left',
|
||||
fontSize: 16,
|
||||
},
|
||||
backgroundColor: '#FFF',
|
||||
line: {
|
||||
legend: {
|
||||
bottom: 0,
|
||||
itemWidth: 25,
|
||||
itemHeight: 14,
|
||||
itemGap: 7,
|
||||
fontSize: 11
|
||||
},
|
||||
},
|
||||
linesPack: {
|
||||
legend: {
|
||||
bottom: 0,
|
||||
itemWidth: 25,
|
||||
itemHeight: 14,
|
||||
itemGap: 7,
|
||||
fontSize: 11
|
||||
},
|
||||
nameXAxis: {
|
||||
padding: [110, 40, 0, 0],
|
||||
}
|
||||
},
|
||||
pie: {
|
||||
legend: {
|
||||
itemWidth: 15,
|
||||
itemHeight: 15,
|
||||
itemGap: 10,
|
||||
fontSize: 12
|
||||
},
|
||||
graphic: {
|
||||
period: {
|
||||
top: 10,
|
||||
right: 30,
|
||||
fontSize: 12
|
||||
},
|
||||
count: {
|
||||
top: 'center',
|
||||
left: 'center',
|
||||
fontSize: 27
|
||||
},
|
||||
}
|
||||
},
|
||||
bar: {
|
||||
legend: {
|
||||
bottom: 10,
|
||||
itemWidth: 25,
|
||||
itemHeight: 14,
|
||||
itemGap: 10,
|
||||
fontSize: 12
|
||||
},
|
||||
grid: {
|
||||
width: '90%',
|
||||
top: 50
|
||||
},
|
||||
yAxis: {
|
||||
label: {fontSize: 13, width: 110, fontWeight: 'normal', position: 'left', offset: 0}
|
||||
|
||||
},
|
||||
xAxis: {
|
||||
label: {fontSize: 10, fontWeight: 'normal', interval: 1, align: 'center'},
|
||||
axisTick: {interval: 0}
|
||||
},
|
||||
dataZoom: {
|
||||
top: 50,
|
||||
start: scrollbarParams(countCharts).start,
|
||||
minSpan: scrollbarParams(countCharts).minSpan,
|
||||
},
|
||||
graphic: {
|
||||
period: {
|
||||
top: 10,
|
||||
right: 60,
|
||||
fontSize: 12
|
||||
},
|
||||
menu: {
|
||||
top: 7,
|
||||
right: 5
|
||||
},
|
||||
container: {
|
||||
top: 27,
|
||||
right: 0
|
||||
},
|
||||
rect: {
|
||||
top: 8,
|
||||
right: 10
|
||||
},
|
||||
download: {
|
||||
top: 18,
|
||||
right: 70
|
||||
},
|
||||
downloadTitle: {
|
||||
top: 23,
|
||||
right: 20
|
||||
},
|
||||
back: {
|
||||
top: 7,
|
||||
right: 30,
|
||||
},
|
||||
link: {
|
||||
top: 28,
|
||||
left: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sizeOptions = (width = 600, height = 400) => {
|
||||
return {
|
||||
width: width,
|
||||
height: height
|
||||
}
|
||||
}
|
||||
|
||||
export {axisBarConfig, windowConfig, sizeOptions}
|
||||
63
live_monitor_vue/src/store/hooks/EventStore.js
Normal file
63
live_monitor_vue/src/store/hooks/EventStore.js
Normal file
@@ -0,0 +1,63 @@
|
||||
export class EventStore {
|
||||
|
||||
constructor(params) {
|
||||
this.store = {}
|
||||
this.params = params
|
||||
}
|
||||
|
||||
storeGetData(data, updateFunc) {
|
||||
if (data.onlyGet) {
|
||||
return this.store[data.key] || null
|
||||
}
|
||||
return updateFunc({
|
||||
newData: data.newData,
|
||||
key: data.key,
|
||||
data: this.store[data.key] || null
|
||||
})
|
||||
}
|
||||
|
||||
storeSetData(data) {
|
||||
return this.store[data.key] = data.value
|
||||
}
|
||||
|
||||
storeRemoveData(data) {
|
||||
return this.store[data.key] = null
|
||||
}
|
||||
|
||||
storeCreateOrGetData(data, updateFunc) {
|
||||
if (this.store[data.key]) {
|
||||
return updateFunc({
|
||||
newData: data.newData,
|
||||
key: data.key,
|
||||
data: this.store[data.key]
|
||||
})
|
||||
}
|
||||
if (!this.store[data.key]) {
|
||||
this.store[data.key] = data.defaultValue
|
||||
return updateFunc({
|
||||
newData: data.newData,
|
||||
key: data.key,
|
||||
data: data.defaultValue
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
localStoreGetData(data, updateFunc) {
|
||||
if (data.onlyGet) {
|
||||
return JSON.parse(localStorage.getItem(data.key)) || null
|
||||
}
|
||||
return updateFunc({
|
||||
newData: data.newData,
|
||||
key: data.key,
|
||||
data: JSON.parse(localStorage.getItem(data.key)) || null
|
||||
})
|
||||
}
|
||||
|
||||
localStoreSetData(data) {
|
||||
return localStorage.setItem(data.key, JSON.stringify(data.value))
|
||||
}
|
||||
|
||||
localStoreRemoveData(data) {
|
||||
return localStorage.removeItem(data.key)
|
||||
}
|
||||
}
|
||||
47
live_monitor_vue/src/store/hooks/PushAfterTimeout.js
Normal file
47
live_monitor_vue/src/store/hooks/PushAfterTimeout.js
Normal file
@@ -0,0 +1,47 @@
|
||||
export class PushAfterTimeout {
|
||||
|
||||
constructor(id, delay = 1000, iconClass = '') {
|
||||
this.el = document.getElementById(id)
|
||||
this.timer = null
|
||||
this.input = this.el.querySelector('input')
|
||||
this.iconClass = iconClass
|
||||
this.makeIconWrapper(this.iconClass)
|
||||
|
||||
this.addLoader()
|
||||
this.timer && clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => {
|
||||
this.removeIcon()
|
||||
this.input.setAttribute("status", "success")
|
||||
this.addSuccess(delay)
|
||||
}, delay)
|
||||
|
||||
}
|
||||
|
||||
addLoader() {
|
||||
this.wrapper.innerHTML = `
|
||||
<div role="status">
|
||||
<svg aria-hidden="true" class="w-4 h-4 text-gray-100 animate-spin dark:text-gray-600 fill-gray-300" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
||||
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
||||
</svg>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
addSuccess(delay) {
|
||||
this.wrapper.innerHTML = `<i class="ri-checkbox-circle-line text-green-500 text-lg"></i>`
|
||||
this.timer = setTimeout(() => {
|
||||
this.removeIcon();
|
||||
}, delay);
|
||||
}
|
||||
removeIcon() {
|
||||
this.wrapper.innerHTML = "";
|
||||
}
|
||||
makeIconWrapper(iconClass) {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.el.appendChild(this.wrapper);
|
||||
this.wrapper.classList = `absolute flex items-center justify-center right-[10px] top-[50%] ${iconClass}`;
|
||||
// this.wrapper.style.right = "10px";
|
||||
// this.wrapper.style.top = "50%";
|
||||
this.wrapper.style.transform = "translateY(-50%)";
|
||||
}
|
||||
}
|
||||
39
live_monitor_vue/src/store/index.js
Normal file
39
live_monitor_vue/src/store/index.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { createStore, createLogger } from 'vuex';
|
||||
import { store as main } from '@/store/modules/main';
|
||||
import { store as machines } from '@/store/modules/machines';
|
||||
import { store as finder_packs } from '@/store/modules/finder_packs';
|
||||
import { store as add_users } from '@/store/modules/add_users';
|
||||
import { store as layout } from '@/store/modules/layout';
|
||||
import { store as auth } from '@/store/modules/auth';
|
||||
import { store as last_packs } from '@/store/modules/last_packs';
|
||||
import { store as last_packs_num } from '@/store/modules/last_packs_num';
|
||||
import { store as packs } from '@/store/modules/packs';
|
||||
import { store as services } from '@/store/modules/services';
|
||||
import { store as logger } from '@/store/modules/logger';
|
||||
|
||||
let debug = process.env.NODE_ENV !== 'production';
|
||||
debug = false;
|
||||
|
||||
const plugins = debug ? [createLogger({})] : [];
|
||||
|
||||
|
||||
export const store = createStore({
|
||||
plugins,
|
||||
modules: {
|
||||
main,
|
||||
machines,
|
||||
finder_packs,
|
||||
add_users,
|
||||
layout,
|
||||
auth,
|
||||
last_packs,
|
||||
last_packs_num,
|
||||
packs,
|
||||
services,
|
||||
logger,
|
||||
},
|
||||
});
|
||||
|
||||
export function useStore() {
|
||||
return store;
|
||||
}
|
||||
588
live_monitor_vue/src/store/modules/add_users/StaticData.js
Normal file
588
live_monitor_vue/src/store/modules/add_users/StaticData.js
Normal file
@@ -0,0 +1,588 @@
|
||||
const columns = [
|
||||
{
|
||||
title: "ID",
|
||||
field: "id",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "ФИО",
|
||||
field: "fio",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Логин",
|
||||
field: "login",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Действия",
|
||||
field: "actions",
|
||||
log: true,
|
||||
render: `
|
||||
<div class="flex items-center gap-2">
|
||||
<a target="_blank" href="/html/admin/manage/{{ id }}?prev_page=/\?current_mode=users"><i class="cursor-pointer ri-pencil-line text-slate-700"></i></a>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
]
|
||||
|
||||
const users = {
|
||||
columns: [
|
||||
{
|
||||
download: false,
|
||||
field: "id",
|
||||
headerFilter: "input",
|
||||
headerFilterPlaceholder: "ID",
|
||||
minWidth: 80,
|
||||
print: false,
|
||||
responsive: 0,
|
||||
sorter: "string",
|
||||
title: "ID",
|
||||
vertAlign: "middle",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
download: false,
|
||||
field: "fio",
|
||||
headerFilter: "input",
|
||||
headerFilterPlaceholder: "ФИО",
|
||||
minWidth: 80,
|
||||
print: false,
|
||||
responsive: 0,
|
||||
sorter: "string",
|
||||
title: "ФИО",
|
||||
vertAlign: "middle",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
download: false,
|
||||
field: "login",
|
||||
headerFilter: "input",
|
||||
headerFilterPlaceholder: "Логин",
|
||||
minWidth: 80,
|
||||
print: false,
|
||||
responsive: 0,
|
||||
sorter: "string",
|
||||
title: "Логин",
|
||||
vertAlign: "middle",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
download: false,
|
||||
field: "actions",
|
||||
headerFilter: "input",
|
||||
headerFilterPlaceholder: "Действия",
|
||||
log: true,
|
||||
minWidth: 80,
|
||||
print: false,
|
||||
render: "\n <div class=\"flex items-center gap-2\">\n <a target=\"_blank\" href=\"/html/admin/manage/{{ id }}?prev_page=/?current_mode=users\"><i class=\"cursor-pointer ri-pencil-line text-slate-700\"></i></a>\n </div>\n ",
|
||||
responsive: 0,
|
||||
sorter: "string",
|
||||
title: "Действия",
|
||||
vertAlign: "middle"
|
||||
}
|
||||
],
|
||||
data: [
|
||||
{
|
||||
adminGroup: {id: 1, name: "ОРИС"},
|
||||
adminGroupId: 1,
|
||||
fio: "Шлыков Сергей Николаевич",
|
||||
firstName: "Сергей",
|
||||
id: 1,
|
||||
insertedAt: "2022-03-10 14:51:07",
|
||||
lastName: "Шлыков",
|
||||
locale: "ru-RU",
|
||||
login: "superuser",
|
||||
notes: "### cHdkIFN1cGVyc2VjcmV0 ###",
|
||||
passwordHash: "$2b$12$vZkno0sNHC1mlHYqXHyG7Ow74WfTY5BmgyGnN3G4l.QUygY2MDfA6",
|
||||
secondName: "Николаевич",
|
||||
updatedAt: "2024-01-15 13:09:35"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "User MNTR ",
|
||||
firstName: "MNTR",
|
||||
id: 3,
|
||||
insertedAt: "2022-03-14 07:12:51",
|
||||
lastName: "User",
|
||||
login: "mntr",
|
||||
notes: "### cHdkIFZuaWlUZXN0 ###",
|
||||
passwordHash: "$2b$12$fWS34Bkerqo8rakLvOX1b.QmlhCWZviQoSPDEBSX1QOiKxC3hY6Aq",
|
||||
updatedAt: "2024-01-15 13:09:35"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Романов Александр ",
|
||||
firstName: "Александр",
|
||||
id: 17,
|
||||
insertedAt: "2022-05-24 15:13:00",
|
||||
lastName: "Романов",
|
||||
login: "RomanovAV",
|
||||
notes: "init pwd: 5Nres551### cHdkIDVOcmVzNTUx ###",
|
||||
passwordHash: "$2b$12$jFDdtPbWQruN0vNmCkEl3uzaWLZxBBYXQC04az6Caj1prQaAz1j8m",
|
||||
updatedAt: "2024-01-15 13:09:35"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Шмаков Александр Сергеевич",
|
||||
firstName: "Александр",
|
||||
id: 18,
|
||||
insertedAt: "2022-05-24 15:13:00",
|
||||
lastName: "Шмаков",
|
||||
login: "smakov",
|
||||
notes: "init pwd: 9ofVZTC6### cHdkIDlvZlZaVEM2 ###",
|
||||
passwordHash: "$2b$12$G./jAcqdse6GWEX5x58hvepFAOeBobvnO7EOWjQeZxLaS4Odqicpe",
|
||||
secondName: "Сергеевич",
|
||||
updatedAt: "2024-01-15 13:09:36"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Моисеев Сергей Александрович",
|
||||
firstName: "Сергей",
|
||||
id: 19,
|
||||
insertedAt: "2022-05-24 15:13:00",
|
||||
lastName: "Моисеев",
|
||||
login: "Moiseyev",
|
||||
notes: "init pwd: tVWRFS61### cHdkIHRWV1JGUzYx ###",
|
||||
passwordHash: "$2b$12$KyUjOK8C1a/EqEmnN7gJYeAwG.4kWXuI2OU1UKXtfSojuIZcnBb6e",
|
||||
secondName: "Александрович",
|
||||
updatedAt: "2024-01-15 13:09:36"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Карнаухов Александр Иванович",
|
||||
firstName: "Александр",
|
||||
id: 20,
|
||||
insertedAt: "2022-05-24 15:13:00",
|
||||
lastName: "Карнаухов",
|
||||
login: "KarnauhovAI",
|
||||
notes: "init pwd: TwgTmHGH### cHdkIFR3Z1RtSEdI ###",
|
||||
passwordHash: "$2b$12$33PWjESsfh7gaDYtddv3EutPFi5bOmqSZBDTp4loiAjLpvhWgj.Hm",
|
||||
secondName: "Иванович",
|
||||
updatedAt: "2024-01-15 13:09:36"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Бобров Виталий Владимирович",
|
||||
firstName: "Виталий",
|
||||
id: 21,
|
||||
insertedAt: "2022-05-24 15:13:01",
|
||||
lastName: "Бобров",
|
||||
login: "Bobrover",
|
||||
notes: "init pwd: xOUM96vI### cHdkIHhPVU05NnZJ ###",
|
||||
passwordHash: "$2b$12$hEameXqEy.FfGJYO7YXnKunud0Wn2ba4GyIaq4WevdA/XmR/iOaM.",
|
||||
secondName: "Владимирович",
|
||||
updatedAt: "2024-01-15 13:09:36"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Бурак Денис Александрович",
|
||||
firstName: "Денис",
|
||||
id: 22,
|
||||
insertedAt: "2022-05-24 15:13:01",
|
||||
lastName: "Бурак",
|
||||
login: "BurakD",
|
||||
notes: "init pwd: fzqRutIC### cHdkIGZ6cVJ1dElD ###",
|
||||
passwordHash: "$2b$12$zUISbezh9hVBAW/U32yWjOCxFYS5vPGPGO92h8P/0TLLYymWqBUGW",
|
||||
secondName: "Александрович",
|
||||
updatedAt: "2024-01-15 13:09:37"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 5, name: "TESTERS"},
|
||||
adminGroupId: 5,
|
||||
fio: "User Test ",
|
||||
firstName: "Test",
|
||||
id: 2,
|
||||
insertedAt: "2022-03-14 07:12:51",
|
||||
lastName: "User",
|
||||
login: "testuser",
|
||||
notes: "### cHdkIE1lZ2FwYXNzd29yZA== ###",
|
||||
passwordHash: "$2b$12$zE14rLaUBdVco9RXE9Hepe0mgkbVIYGa4CwiNrZHbHI0SVCNUVQhW",
|
||||
updatedAt: "2024-01-15 13:50:49"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Ярцев Андрей Васильевич",
|
||||
firstName: "Андрей",
|
||||
id: 16,
|
||||
insertedAt: "2022-05-24 15:12:59",
|
||||
lastName: "Ярцев",
|
||||
login: "Yartsev.Andrey",
|
||||
notes: "init pwd: iWA8qWsv### cHdkIGlXQThxV3N2 ###",
|
||||
passwordHash: "$2b$12$EAhw4.dUdnSlki0tiFyKI.9HkzfviOYCxqBj/63CNU68rg0idna9i",
|
||||
secondName: "Васильевич",
|
||||
updatedAt: "2024-01-15 13:53:07"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Данилов Александр Николаевич",
|
||||
firstName: "Александр",
|
||||
id: 23,
|
||||
insertedAt: "2022-05-24 15:13:01",
|
||||
lastName: "Данилов",
|
||||
login: "Danilov.Aleksandr",
|
||||
notes: "init pwd: hEVonP0H### cHdkIGhFVm9uUDBI ###",
|
||||
passwordHash: "$2b$12$B1RtRKd7g6fbqYhXfZ9i/e0cVG9CQzI64D37XplU8yUgHz96EX1HO",
|
||||
secondName: "Николаевич",
|
||||
updatedAt: "2024-01-15 13:09:37"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Буторин Евгений Львович",
|
||||
firstName: "Евгений",
|
||||
id: 24,
|
||||
insertedAt: "2022-05-24 15:13:01",
|
||||
lastName: "Буторин",
|
||||
login: "butorin",
|
||||
notes: "init pwd: wLsqjX+H### cHdkIHdMc3FqWCtI ###",
|
||||
passwordHash: "$2b$12$.MMVjOpL1ol4jR6n/b8S9exedOszcsxPbShcWdYOEggtGFaDjnJuG",
|
||||
secondName: "Львович",
|
||||
updatedAt: "2024-01-15 13:09:37"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Иванов Дмитрий ",
|
||||
firstName: "Дмитрий",
|
||||
id: 25,
|
||||
insertedAt: "2022-05-24 15:13:01",
|
||||
lastName: "Иванов",
|
||||
login: "ivanovd",
|
||||
notes: "init pwd: OzaruSHc### cHdkIE96YXJ1U0hj ###",
|
||||
passwordHash: "$2b$12$GBTNgWIRGtlUlfjneY8CCuwkXI5bYnBvjtWRz/u/MQx0fi9VKXfHW",
|
||||
updatedAt: "2024-01-15 13:09:37"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Ильичев Михаил Сергеевич",
|
||||
firstName: "Михаил",
|
||||
id: 33,
|
||||
insertedAt: "2022-05-24 15:13:03",
|
||||
lastName: "Ильичев",
|
||||
login: "ilyichev.mikhail",
|
||||
notes: "init pwd: NxLW6Xmt### cHdkIE54TFc2WG10 ###",
|
||||
passwordHash: "$2b$12$P8oiAcMG5m0XvuKLy4Dtve8hw7xnczVDnFGwo0eP7HkEKijQyNOWG",
|
||||
secondName: "Сергеевич",
|
||||
updatedAt: "2024-01-15 13:09:37"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Карелин Артём Дмитриевич",
|
||||
firstName: "Артём",
|
||||
id: 34,
|
||||
insertedAt: "2022-05-24 15:13:03",
|
||||
lastName: "Карелин",
|
||||
login: "karelin.artem",
|
||||
notes: "init pwd: +KD43KXa### cHdkICtLRDQzS1hh ###",
|
||||
passwordHash: "$2b$12$Z6nhsAQLtG5qDndjgHFhzu/AP9oGWEejtWznjua2T/SRLSutLNy.y",
|
||||
secondName: "Дмитриевич",
|
||||
updatedAt: "2024-01-15 13:09:38"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Машкова Юлия Сергеевна",
|
||||
firstName: "Юлия",
|
||||
id: 35,
|
||||
insertedAt: "2022-05-24 15:13:04",
|
||||
lastName: "Машкова",
|
||||
login: "mashkova",
|
||||
notes: "init pwd: fCD8a2ub### cHdkIGZDRDhhMnVi ###",
|
||||
passwordHash: "$2b$12$D61pVAGUHUXaYF761n0vTuHE7XpO.KG.4FcpfHm3y7IffZuR4OPH2",
|
||||
secondName: "Сергеевна",
|
||||
updatedAt: "2024-01-15 13:09:38"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Головко Борис Борисович",
|
||||
firstName: "Борис",
|
||||
id: 26,
|
||||
insertedAt: "2022-05-24 15:13:02",
|
||||
lastName: "Головко",
|
||||
login: "GolovkoBB",
|
||||
notes: "init pwd: T8mlO00M### cHdkIFQ4bWxPMDBN ###",
|
||||
passwordHash: "$2b$12$BSWzgTFpgit9Vz0hKtGqUuBa.bn6SL.qi7ra8LoYo0hp6AR5NfO1m",
|
||||
secondName: "Борисович",
|
||||
updatedAt: "2024-01-15 13:09:38"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 1, name: "ОРИС"},
|
||||
adminGroupId: 1,
|
||||
fio: "Перевязкин Александр Александрович",
|
||||
firstName: "Александр",
|
||||
id: 36,
|
||||
insertedAt: "2022-05-24 15:13:04",
|
||||
lastName: "Перевязкин",
|
||||
login: "Perevyazkin.Alexandr",
|
||||
notes: "init pwd: RCZZfFGe### cHdkIFJDWlpmRkdl ###",
|
||||
passwordHash: "$2b$12$t0VnSxU8Rirbu/50R3JXTeK4fwefglZO8kNC98yDO1FocnUCVTnsW",
|
||||
secondName: "Александрович",
|
||||
updatedAt: "2024-01-15 13:09:39"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Гордейчук Алексей Владимирович",
|
||||
firstName: "Алексей",
|
||||
id: 27,
|
||||
insertedAt: "2022-05-24 15:13:02",
|
||||
lastName: "Гордейчук",
|
||||
login: "GordeychukAV",
|
||||
notes: "init pwd: b9fmKp1y### cHdkIGI5Zm1LcDF5 ###",
|
||||
passwordHash: "$2b$12$mg7xtqxgDgFovnmqfRy5reb0pL1.XWuXxQzcr6L1OCpAtWRXFrsBW",
|
||||
secondName: "Владимирович",
|
||||
updatedAt: "2024-01-15 13:09:39"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 1, name: "ОРИС"},
|
||||
adminGroupId: 1,
|
||||
fio: "Котов Кирилл Юрьевич",
|
||||
firstName: "Кирилл",
|
||||
id: 28,
|
||||
insertedAt: "2022-05-24 15:13:02",
|
||||
lastName: "Котов",
|
||||
login: "kotov",
|
||||
notes: "init pwd: 9la1NqNC### cHdkIDlsYTFOcU5D ###",
|
||||
passwordHash: "$2b$12$yIgJUi47b8NcPLVMaTh2SezTbSdXe7kbMWlNd92p/JxxeyK6PK5vm",
|
||||
secondName: "Юрьевич",
|
||||
updatedAt: "2024-01-15 13:09:39"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Шульгин Алексей Викторович",
|
||||
firstName: "Алексей",
|
||||
id: 29,
|
||||
insertedAt: "2022-05-24 15:13:02",
|
||||
lastName: "Шульгин",
|
||||
login: "Schulgin.Aleksey",
|
||||
notes: "init pwd: MULd4uDy### cHdkIE1VTGQ0dUR5 ###",
|
||||
passwordHash: "$2b$12$rez8rGGiHvHgkxIffxCyp.Ahne1rfgg5khXKLrwmNvwDVrpal0v1a",
|
||||
secondName: "Викторович",
|
||||
updatedAt: "2024-01-15 13:09:39"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Егоров Максим Валерьевич",
|
||||
firstName: "Максим",
|
||||
id: 30,
|
||||
insertedAt: "2022-05-24 15:13:03",
|
||||
lastName: "Егоров",
|
||||
login: "Egorov.Maxim",
|
||||
notes: "init pwd: uV4ZwuGv### cHdkIHVWNFp3dUd2 ###",
|
||||
passwordHash: "$2b$12$vq32dLtEBAOcvME6h5Bb0.sHGa1DryRwoLcCIyzfcJsisZZ.MvyEi",
|
||||
secondName: "Валерьевич",
|
||||
updatedAt: "2024-01-15 13:09:39"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Скляров Алексей Николаевич",
|
||||
firstName: "Алексей",
|
||||
id: 31,
|
||||
insertedAt: "2022-05-24 15:13:03",
|
||||
lastName: "Скляров",
|
||||
login: "Sklyarov.Aleksey",
|
||||
notes: "init pwd: RpE5Hy+3### cHdkIFJwRTVIeSsz ###",
|
||||
passwordHash: "$2b$12$Y35oJ8/99n5sryYOoNTu5eYV77fIvgIA2OFkQ26Nw1b3FlXepUx9i",
|
||||
secondName: "Николаевич",
|
||||
updatedAt: "2024-01-15 13:09:40"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 1, name: "ОРИС"},
|
||||
adminGroupId: 1,
|
||||
fio: "Test Admin ",
|
||||
firstName: "Admin",
|
||||
id: 47,
|
||||
insertedAt: "2023-03-02 08:06:05",
|
||||
lastName: "Test",
|
||||
login: "ta112",
|
||||
notes: "### cHdkIFlROEJ1TG5n ###",
|
||||
passwordHash: "$2b$12$6nuL1xC72rUt30YyGqqr6.5yNQ2vvvQlj2ZJeLrKJ9evqqDE75zAC",
|
||||
phone: "444",
|
||||
updatedAt: "2024-01-15 13:09:43"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 8, name: "ru_watcher"},
|
||||
adminGroupId: 8,
|
||||
fio: "Packs Manger ",
|
||||
firstName: "Manger",
|
||||
id: 48,
|
||||
insertedAt: "2023-03-15 11:25:23",
|
||||
lastName: "Packs",
|
||||
login: "packs_manger",
|
||||
notes: "### cHdkIFBhY2sxc3Nz ###",
|
||||
passwordHash: "$2b$12$rW9uGhrLum.2pTBYgtumAuy8xCqI9iFiUJ4cprGywYLUKbt/XNW4S",
|
||||
updatedAt: "2024-01-15 13:09:43"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 1, name: "ОРИС"},
|
||||
adminGroupId: 1,
|
||||
fio: "Иванова Александра Сергеевна",
|
||||
firstName: "Александра",
|
||||
id: 37,
|
||||
insertedAt: "2022-05-25 07:16:20",
|
||||
lastName: "Иванова",
|
||||
login: "sascha",
|
||||
notes: "init pwd: Lenovoinwin### cHdkIExlbm92b2lud2lu ###",
|
||||
passwordHash: "$2b$12$xLWXBi0AYp.QOohHGaURYOp0cuow5eTT24oh1HNXI.ZcOCtzvsVUG",
|
||||
secondName: "Сергеевна",
|
||||
updatedAt: "2024-01-15 13:09:40"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "test_admin_del d d",
|
||||
firstName: "d",
|
||||
id: 49,
|
||||
insertedAt: "2023-03-27 07:31:25",
|
||||
lastName: "test_admin_del",
|
||||
login: "test_admin_del",
|
||||
notes: "### cHdkIHpNSjJ0bVhr ###",
|
||||
passwordHash: "$2b$12$Z0tEFtGnI1iO9kjFXYpIXOwYldhRlNkIcei002qzAy7HVJa7m/I.e",
|
||||
secondName: "d",
|
||||
updatedAt: "2024-01-15 13:09:43"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 9, name: "kudt_watcher"},
|
||||
adminGroupId: 9,
|
||||
fio: "KUDT Watcher ",
|
||||
firstName: "Watcher",
|
||||
id: 50,
|
||||
insertedAt: "2023-03-28 07:55:01",
|
||||
lastName: "KUDT",
|
||||
login: "KUDT",
|
||||
notes: "### cHdkIFdhdGNoS3VkdA== ###",
|
||||
passwordHash: "$2b$12$r47Teqz/KONX3fx2nVEHtup.NRHynjDqobBmNCV7t7fALQwvFXE.a",
|
||||
updatedAt: "2024-01-15 13:09:43"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Акулов Егор Пользователь",
|
||||
firstName: "Егор",
|
||||
id: 52,
|
||||
insertedAt: "2023-07-31 12:17:56",
|
||||
lastName: "Акулов",
|
||||
login: "AkulEgor",
|
||||
notes: "### cHdkIDEyQWt1bDEz ###",
|
||||
passwordHash: "$2b$12$37YN2TmjAZjE9dmZXycI4OBKMGVUg.vLcFKwx0sS7mn96hB8V1Iai",
|
||||
secondName: "Пользователь",
|
||||
updatedAt: "2024-01-15 13:09:44"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 12, name: "free_packs_watcher"},
|
||||
adminGroupId: 12,
|
||||
fio: "Кузнецов Александр Юрьевич",
|
||||
firstName: "Александр",
|
||||
id: 51,
|
||||
insertedAt: "2023-04-25 09:25:08",
|
||||
lastName: "Кузнецов",
|
||||
login: "KuznetsovAY",
|
||||
notes: "### cHdkIEt1ejU2N2F5 ###",
|
||||
passwordHash: "$2b$12$WF7iMKiDxKcWShCGTgWnNu4b1HHCxFIP7YPR60LJlwc/BCBwh575q",
|
||||
secondName: "Юрьевич",
|
||||
updatedAt: "2024-01-15 13:09:44"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 8, name: "ru_watcher"},
|
||||
adminGroupId: 8,
|
||||
fio: "Ru Watcher ",
|
||||
firstName: "Watcher",
|
||||
id: 46,
|
||||
insertedAt: "2023-03-02 06:31:45",
|
||||
lastName: "Ru",
|
||||
login: "RuWatcher",
|
||||
notes: "### cHdkIEpiWlBqZE05 ###",
|
||||
passwordHash: "$2b$12$naoRmmTeR6WYy6UZWHxFBuhwO3s52/p/MCkZlP.KgWsXrk8sIUz4W",
|
||||
phone: "13",
|
||||
updatedAt: "2024-01-15 13:09:40"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Ананьев Виталий Сергеевич",
|
||||
firstName: "Виталий",
|
||||
id: 38,
|
||||
insertedAt: "2022-05-31 06:34:02",
|
||||
lastName: "Ананьев",
|
||||
login: "ananev.vitaly",
|
||||
notes: "### cHdkIHNKVWl4U0Zh ###",
|
||||
passwordHash: "$2b$12$JLiIog4a9z2dt8xFP.s/.OBcLHo4cDJgLpCjCxHVQa1rAjmNCIOZG",
|
||||
secondName: "Сергеевич",
|
||||
updatedAt: "2024-01-15 13:09:41"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 15, name: "Монтаж"},
|
||||
adminGroupId: 15,
|
||||
fio: "Карпенко Иван Васильевич",
|
||||
firstName: "Иван",
|
||||
id: 39,
|
||||
insertedAt: "2022-06-02 09:28:49",
|
||||
lastName: "Карпенко",
|
||||
login: "karpenko.ivan",
|
||||
notes: "### cHdkIDdaNXM3Tldq ###",
|
||||
passwordHash: "$2b$12$she6mqurkOULGSd.PzKxeeMppEDjEMhXPdjuH.zncLkVZEKw0iWT2",
|
||||
secondName: "Васильевич",
|
||||
updatedAt: "2024-01-15 13:09:41"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 5, name: "TESTERS"},
|
||||
adminGroupId: 5,
|
||||
email: "HasyanovMark@no-mail.xx",
|
||||
fio: "Хасянов Марк х",
|
||||
firstName: "Марк",
|
||||
id: 53,
|
||||
insertedAt: "2023-09-22 10:27:21",
|
||||
lastName: "Хасянов",
|
||||
login: "HasyanovM",
|
||||
notes: "### cHdkIG5ZdzE4dTk2 ###",
|
||||
passwordHash: "$2b$12$Pf2T.EKEWXhE2XBpridtOOuKU6NO5evZoGKGMwg4u1wEtCa1hNxUK",
|
||||
secondName: "х",
|
||||
updatedAt: "2024-01-15 13:09:44"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 6, name: "ПКФ"},
|
||||
adminGroupId: 6,
|
||||
fio: "Технологии ПКФ ",
|
||||
firstName: "ПКФ",
|
||||
id: 40,
|
||||
insertedAt: "2022-06-02 11:53:48",
|
||||
lastName: "Технологии",
|
||||
login: "pkf_teck",
|
||||
notes: "### cHdkIEFCWDIwSXU3 ###",
|
||||
passwordHash: "$2b$12$JFef9mZEyWmPENZM2VTTWePVfGogTAgnu4dFYHNMDhY6RPWb7zRXG",
|
||||
updatedAt: "2024-01-15 13:09:41"
|
||||
},
|
||||
{
|
||||
adminGroup: {id: 7, name: "ВНИКТ"},
|
||||
adminGroupId: 7,
|
||||
fio: "Ушанов Андрей ",
|
||||
firstName: "Андрей",
|
||||
id: 41,
|
||||
insertedAt: "2022-06-06 10:02:15",
|
||||
lastName: "Ушанов",
|
||||
login: "AUshanov",
|
||||
notes: "### cHdkIFBPU2t1anox ###",
|
||||
passwordHash: "$2b$12$StGexN61k2AGoBnCaC2GSeoYrVRGplRoeOSwXXpj3OJiHoLq9zKCi",
|
||||
updatedAt: "2024-01-15 13:09:41"
|
||||
},
|
||||
],
|
||||
height: "200px"
|
||||
}
|
||||
|
||||
export { columns, users }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user