Versão 15h12 11/02
This commit is contained in:
parent
22bd184ccc
commit
beead0b6b2
@ -14,15 +14,67 @@
|
|||||||
<v-btn color="primary" class="mb-3" @click="exportUsersPDF">Exportar PDF</v-btn>
|
<v-btn color="primary" class="mb-3" @click="exportUsersPDF">Exportar PDF</v-btn>
|
||||||
|
|
||||||
<v-data-table :headers="userHeaders" :items="filteredUsers" item-value="id">
|
<v-data-table :headers="userHeaders" :items="filteredUsers" item-value="id">
|
||||||
<template v-slot:itemactions="{ item }">
|
<template v-slot:item.actions="{ item }">
|
||||||
<v-btn icon @click="editUser(item)">
|
<v-btn icon="mdi-pencil" size="small" color="info" variant="text" density="compact" @click="openUserEdit(item)">
|
||||||
<v-icon>mdi-pencil</v-icon>
|
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn icon @click="deleteUser(item)">
|
<v-btn icon="mdi-delete" size="small" color="error" variant="text" density="compact" @click="deleteUser(item)">
|
||||||
<v-icon>mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
|
|
||||||
|
<!-- Diálogo de Edição de Usuário -->
|
||||||
|
<v-dialog v-model="showUserEdit" max-width="600px">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
<span class="text-h5">Editar Usuário</span>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
v-model="editedUser.name"
|
||||||
|
label="Nome"
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
v-model="editedUser.email"
|
||||||
|
label="E-mail"
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-select
|
||||||
|
v-model="editedUser.role"
|
||||||
|
:items="['Administrador', 'Usuário']"
|
||||||
|
label="Cargo"
|
||||||
|
required
|
||||||
|
></v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-select
|
||||||
|
v-model="editedUser.group"
|
||||||
|
:items="['Alfa', 'Beta', 'Omega']"
|
||||||
|
label="Grupo"
|
||||||
|
required
|
||||||
|
></v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="blue-darken-1" variant="text" @click="closeUserEdit">
|
||||||
|
Cancelar
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="blue-darken-1" variant="text" @click="saveUserEdit">
|
||||||
|
Salvar
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
|
|
||||||
@ -33,20 +85,134 @@
|
|||||||
<v-btn color="primary" class="mb-3" @click="exportCamerasPDF">Exportar PDF</v-btn>
|
<v-btn color="primary" class="mb-3" @click="exportCamerasPDF">Exportar PDF</v-btn>
|
||||||
|
|
||||||
<v-data-table :headers="cameraHeaders" :items="filteredCameras" item-value="model">
|
<v-data-table :headers="cameraHeaders" :items="filteredCameras" item-value="model">
|
||||||
<template v-slot:itemview="{ item }">
|
<template v-slot:item.view="{ item }">
|
||||||
<v-btn icon @click="openCameraView(item.view)">
|
<v-chip
|
||||||
<v-icon>mdi-video</v-icon>
|
:color="getStreamTypeColor(item.view)"
|
||||||
|
size="small"
|
||||||
|
class="text-caption"
|
||||||
|
>
|
||||||
|
{{ getStreamType(item.view) }}
|
||||||
|
</v-chip>
|
||||||
|
<v-btn icon="mdi-video" size="small" color="primary" variant="text" density="compact" @click="openCameraView(item)" class="ml-2">
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:itemactions="{ item }">
|
<template v-slot:item.actions="{ item }">
|
||||||
<v-btn icon @click="editCamera(item)">
|
<v-btn icon="mdi-pencil" size="small" color="info" variant="text" density="compact" @click="editCamera(item)">
|
||||||
<v-icon>mdi-pencil</v-icon>
|
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn icon @click="deleteCamera(item)">
|
<v-btn icon="mdi-delete" size="small" color="error" variant="text" density="compact" @click="deleteCamera(item)">
|
||||||
<v-icon>mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
|
|
||||||
|
<!-- Diálogo de Edição de Câmera -->
|
||||||
|
<v-dialog v-model="showCameraEdit" max-width="600px">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
<span class="text-h5">Editar Câmera</span>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form ref="form" v-model="validForm">
|
||||||
|
<v-text-field
|
||||||
|
v-model="editedCamera.model"
|
||||||
|
label="Modelo"
|
||||||
|
required
|
||||||
|
:rules="[v => !!v || 'Modelo é obrigatório']"
|
||||||
|
class="mb-4"
|
||||||
|
></v-text-field>
|
||||||
|
|
||||||
|
<v-text-field
|
||||||
|
v-model="editedCamera.description"
|
||||||
|
label="Descrição"
|
||||||
|
required
|
||||||
|
:rules="[v => !!v || 'Descrição é obrigatória']"
|
||||||
|
class="mb-4"
|
||||||
|
></v-text-field>
|
||||||
|
|
||||||
|
<v-text-field
|
||||||
|
v-model="editedCamera.view"
|
||||||
|
label="URL do Stream"
|
||||||
|
required
|
||||||
|
:rules="[
|
||||||
|
v => !!v || 'URL é obrigatória',
|
||||||
|
v => isValidStreamUrl(v) || 'URL deve começar com http:// ou rtsp://'
|
||||||
|
]"
|
||||||
|
class="mb-4"
|
||||||
|
></v-text-field>
|
||||||
|
|
||||||
|
<v-select
|
||||||
|
v-model="editedCamera.status"
|
||||||
|
:items="['Ativa', 'Inativa', 'Manutenção']"
|
||||||
|
label="Status"
|
||||||
|
required
|
||||||
|
:rules="[v => !!v || 'Status é obrigatório']"
|
||||||
|
class="mb-4"
|
||||||
|
></v-select>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="error" variant="text" @click="closeCameraEdit">
|
||||||
|
Cancelar
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="success" variant="text" @click="saveCameraEdit">
|
||||||
|
Salvar
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- Diálogo de Visualização de Câmera -->
|
||||||
|
<v-dialog v-model="showCameraView" fullscreen hide-overlay>
|
||||||
|
<v-card>
|
||||||
|
<v-toolbar dark color="primary">
|
||||||
|
<v-btn icon dark @click="closeCameraView">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-toolbar-title>Visualização da Câmera</v-toolbar-title>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-card-text>
|
||||||
|
<div class="camera-container">
|
||||||
|
<div v-if="isRTSP" class="video-player">
|
||||||
|
<v-alert
|
||||||
|
type="info"
|
||||||
|
title="Stream RTSP"
|
||||||
|
text="Conectando ao stream RTSP. Por favor, aguarde..."
|
||||||
|
class="mb-4"
|
||||||
|
></v-alert>
|
||||||
|
<video-js
|
||||||
|
ref="videoPlayer"
|
||||||
|
class="vjs-custom-skin"
|
||||||
|
controls
|
||||||
|
preload="auto"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
>
|
||||||
|
<source :src="rtspToHls(selectedCameraUrl)" type="application/x-mpegURL">
|
||||||
|
</video-js>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="isHTTP" class="video-player">
|
||||||
|
<video-js
|
||||||
|
ref="videoPlayer"
|
||||||
|
class="vjs-custom-skin"
|
||||||
|
controls
|
||||||
|
preload="auto"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
>
|
||||||
|
<source :src="selectedCameraUrl" :type="getVideoType(selectedCameraUrl)">
|
||||||
|
</video-js>
|
||||||
|
</div>
|
||||||
|
<div v-else class="error-message">
|
||||||
|
<v-alert
|
||||||
|
type="error"
|
||||||
|
title="Erro"
|
||||||
|
text="Formato de URL não suportado. Use URLs que comecem com http:// ou rtsp://"
|
||||||
|
></v-alert>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
</v-window>
|
</v-window>
|
||||||
@ -54,12 +220,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed, onMounted } from "vue";
|
||||||
import { jsPDF } from "jspdf";
|
import { jsPDF } from "jspdf";
|
||||||
import "jspdf-autotable";
|
import "jspdf-autotable";
|
||||||
|
import VideoPlayer from 'vue-video-player';
|
||||||
|
import 'video.js/dist/video-js.css';
|
||||||
|
|
||||||
|
// Estado dos tabs
|
||||||
const tab = ref("usuarios");
|
const tab = ref("usuarios");
|
||||||
|
|
||||||
|
// Estado dos diálogos
|
||||||
|
const showUserEdit = ref(false);
|
||||||
|
const showCameraView = ref(false);
|
||||||
|
const editedUser = ref({});
|
||||||
|
const selectedCameraUrl = ref('');
|
||||||
|
|
||||||
// **Usuários**
|
// **Usuários**
|
||||||
const userHeaders = [
|
const userHeaders = [
|
||||||
{ title: "ID", key: "id" },
|
{ title: "ID", key: "id" },
|
||||||
@ -86,17 +261,6 @@ const filteredUsers = computed(() =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// **Exportação PDF Usuários**
|
|
||||||
const exportUsersPDF = () => {
|
|
||||||
const doc = new jsPDF();
|
|
||||||
doc.text("Lista de Usuários", 14, 10);
|
|
||||||
doc.autoTable({
|
|
||||||
head: [["ID", "Nome", "E-mail", "Cargo", "Grupo"]],
|
|
||||||
body: users.value.map(u => [u.id, u.name, u.email, u.role, u.group])
|
|
||||||
});
|
|
||||||
doc.save("usuarios.pdf");
|
|
||||||
};
|
|
||||||
|
|
||||||
// **Câmeras**
|
// **Câmeras**
|
||||||
const cameraHeaders = [
|
const cameraHeaders = [
|
||||||
{ title: "Modelo", key: "model" },
|
{ title: "Modelo", key: "model" },
|
||||||
@ -112,6 +276,19 @@ const cameras = ref([
|
|||||||
{ model: "AK-31548", description: "Câmera IP", view: "rtsp://admin:706726do@farmacia1111.ddns-intelbras.com.br:58589/cam/realmonitor?channel=3&subtype=0", status: "Ativa" }
|
{ model: "AK-31548", description: "Câmera IP", view: "rtsp://admin:706726do@farmacia1111.ddns-intelbras.com.br:58589/cam/realmonitor?channel=3&subtype=0", status: "Ativa" }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Funções auxiliares para identificação do tipo de stream
|
||||||
|
const getStreamType = (url) => {
|
||||||
|
if (url.toLowerCase().startsWith('rtsp://')) return 'RTSP';
|
||||||
|
if (url.toLowerCase().startsWith('http')) return 'HTTP';
|
||||||
|
return 'Desconhecido';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStreamTypeColor = (url) => {
|
||||||
|
if (url.toLowerCase().startsWith('rtsp://')) return 'purple';
|
||||||
|
if (url.toLowerCase().startsWith('http')) return 'blue';
|
||||||
|
return 'gray';
|
||||||
|
};
|
||||||
|
|
||||||
const searchCamera = ref("");
|
const searchCamera = ref("");
|
||||||
const filteredCameras = computed(() =>
|
const filteredCameras = computed(() =>
|
||||||
cameras.value.filter(camera =>
|
cameras.value.filter(camera =>
|
||||||
@ -121,21 +298,52 @@ const filteredCameras = computed(() =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// **Exportação PDF Câmeras**
|
// Computed properties para o player de vídeo
|
||||||
const exportCamerasPDF = () => {
|
const isRTSP = computed(() => selectedCameraUrl.value.toLowerCase().startsWith('rtsp://'));
|
||||||
const doc = new jsPDF();
|
const isHTTP = computed(() => selectedCameraUrl.value.toLowerCase().startsWith('http'));
|
||||||
doc.text("Lista de Câmeras", 14, 10);
|
|
||||||
doc.autoTable({
|
const playerOptions = computed(() => ({
|
||||||
head: [["Modelo", "Descrição", "Visualização", "Status"]],
|
autoplay: true,
|
||||||
body: cameras.value.map(c => [c.model, c.description, c.view, c.status])
|
controls: true,
|
||||||
});
|
sources: [{
|
||||||
doc.save("cameras.pdf");
|
src: selectedCameraUrl.value,
|
||||||
};
|
type: 'application/x-mpegURL'
|
||||||
|
}]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// [Resto do código permanece o mesmo...]
|
||||||
|
|
||||||
// **Ações**
|
|
||||||
const openCameraView = (url) => window.open(url, "_blank");
|
|
||||||
const editUser = (item) => alert(`Editar usuário ${item.name}`);
|
|
||||||
const deleteUser = (item) => alert(`Deletar usuário ${item.name}`);
|
|
||||||
const editCamera = (item) => alert(`Editar câmera ${item.model}`);
|
|
||||||
const deleteCamera = (item) => alert(`Deletar câmera ${item.model}`);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.camera-container {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 64px);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: calc(100vh - 64px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-custom-skin {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user