Adiciona rota para novo video
This commit is contained in:
@@ -5,7 +5,8 @@ import { createWebHistory, createRouter } from "vue-router";
|
||||
import roles from "@/auth/roles";
|
||||
|
||||
import Videos from "@/routes/videos";
|
||||
import Login from "@/routes/auth/Login.vue";
|
||||
import NewVideo from "@/routes/videos/new";
|
||||
import Login from "@/routes/auth/Login";
|
||||
|
||||
const getUserRoles = () => {
|
||||
const rolesFromCookie = Cookies.get("user_roles"); // TODO: Tirar as permissões do usuário
|
||||
@@ -34,6 +35,17 @@ export const routes = [
|
||||
showinModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/videos/new",
|
||||
name: "newVideo",
|
||||
component: NewVideo,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: "Novo Vídeo",
|
||||
permissions: [roles.VIDEOS_LIST],
|
||||
showinModal: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
meta: {
|
||||
|
||||
34
src/components/DisplayValue/index.vue
Normal file
34
src/components/DisplayValue/index.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="display-label">{{ label }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>{{ value }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "DisplayValue",
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.display-label {
|
||||
font-weight: bold;
|
||||
color: gray;
|
||||
}
|
||||
</style>
|
||||
67
src/components/Dropdown/index.vue
Normal file
67
src/components/Dropdown/index.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="label">
|
||||
<span>{{ label }} {{ required ? "*" : "" }}</span>
|
||||
</div>
|
||||
|
||||
<slot
|
||||
name="select"
|
||||
:model-value="modelValue"
|
||||
:update:model-value="updateModelValue"
|
||||
>
|
||||
<q-select
|
||||
outlined
|
||||
:model-value="modelValue"
|
||||
@update:model-value="updateModelValue"
|
||||
:multiple="multiple"
|
||||
:options="options"
|
||||
:loading="loading"
|
||||
:clearable="clearable"
|
||||
:disable="loading || disable"
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number, Array, Object],
|
||||
default: null,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const updateModelValue = (value) => {
|
||||
emit("update:modelValue", value);
|
||||
};
|
||||
</script>
|
||||
@@ -4,28 +4,56 @@
|
||||
<span>{{ label }} {{ required ? "*" : "" }}</span>
|
||||
</div>
|
||||
|
||||
<q-input outlined v-model="text" />
|
||||
<q-input
|
||||
outlined
|
||||
:model-value="modelValue"
|
||||
@update:model-value="updateValue"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<slot name="append"></slot>
|
||||
</template>
|
||||
|
||||
<template v-slot:prepend>
|
||||
<slot name="prepend"></slot>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from "vue";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "TextField",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
emits: ["update:modelValue"],
|
||||
setup(props, { emit }) {
|
||||
const updateValue = (value) => {
|
||||
emit("update:modelValue", value);
|
||||
};
|
||||
|
||||
return {
|
||||
text: ref(""),
|
||||
updateValue,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,10 +2,10 @@ import axios from "axios";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
export const API = axios.create({
|
||||
baseURL:
|
||||
process.env.NODE_ENV === "development"
|
||||
? "https://api.clipperia.com.br"
|
||||
: "http://nestjs:3000",
|
||||
baseURL: "http://localhost:3000",
|
||||
// process.env.NODE_ENV === "development"
|
||||
// ? "https://api.clipperia.com.br"
|
||||
// : "http://nestjs:3000",
|
||||
});
|
||||
|
||||
API.interceptors.request.use((config) => {
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
<TextField label="Video ID" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-select outlined :options="['Ativo', 'Inativo']" label="Situação" />
|
||||
<Dropdown
|
||||
v-model="select"
|
||||
label="Situação"
|
||||
clearable
|
||||
:options="situations"
|
||||
:loading="situationsLoading"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,6 +35,9 @@
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<Button label="Teste" @click="handleTeste()" fullWidth />
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
|
||||
@@ -50,12 +59,17 @@
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<q-page-sticky position="bottom-right" :offset="[20, 70]">
|
||||
<q-btn fab icon="add" color="accent" @click="handleAddVideo" />
|
||||
</q-page-sticky>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from "@components/Button";
|
||||
import Table from "@components/Table";
|
||||
import TextField from "@components/TextField";
|
||||
import Dropdown from "@components/Dropdown";
|
||||
|
||||
import { API } from "@config/axios";
|
||||
import { getErrorMessage } from "@utils/axios";
|
||||
@@ -97,6 +111,7 @@ export default {
|
||||
Button,
|
||||
Table,
|
||||
TextField,
|
||||
Dropdown,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -112,17 +127,28 @@ export default {
|
||||
hasNext: false,
|
||||
hasPrev: false,
|
||||
},
|
||||
situations: [],
|
||||
situationsLoading: false,
|
||||
select: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getSituation();
|
||||
},
|
||||
methods: {
|
||||
async handleSearch(pagination) {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
const baseParams = {
|
||||
situation: this.select,
|
||||
};
|
||||
|
||||
const { data } = await API.get("/videos", {
|
||||
params: {
|
||||
perPage: pagination.perPage,
|
||||
page: pagination.page,
|
||||
...baseParams,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -140,6 +166,41 @@ export default {
|
||||
updatePagination(pagination) {
|
||||
this.handleSearch(pagination);
|
||||
},
|
||||
async getSituation() {
|
||||
try {
|
||||
this.situationsLoading = true;
|
||||
|
||||
const { data } = await API.get("/videos/situacoes");
|
||||
|
||||
this.situations = data;
|
||||
} catch (error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
message: getErrorMessage(error, "Erro ao buscar situações"),
|
||||
});
|
||||
} finally {
|
||||
this.situationsLoading = false;
|
||||
}
|
||||
},
|
||||
async handleTeste() {
|
||||
try {
|
||||
const { data } = await API.get("/videos/search", {
|
||||
params: {
|
||||
url: "https://www.youtube.com/watch?v=x9-YRAYhesI",
|
||||
},
|
||||
});
|
||||
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
message: getErrorMessage(error, "Erro ao buscar situações"),
|
||||
});
|
||||
}
|
||||
},
|
||||
handleAddVideo() {
|
||||
this.$router.push("/videos/new");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
178
src/routes/videos/new.vue
Normal file
178
src/routes/videos/new.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="user-list q-pa-md">
|
||||
<q-card
|
||||
flat
|
||||
bordered
|
||||
class="q-pa-sm q-mb-lg"
|
||||
:class="{
|
||||
'bg-grey-2': !$q.dark.isActive,
|
||||
}"
|
||||
>
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-12">
|
||||
<TextField label="URL do Vídeo" v-model="url" :disabled="loading" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-6">
|
||||
<Button
|
||||
label="Buscar Informações"
|
||||
:disabled="!url || loading"
|
||||
@click="handleSearch"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
|
||||
<q-card
|
||||
flat
|
||||
bordered
|
||||
class="q-pa-sm q-mb-lg"
|
||||
:class="{
|
||||
'bg-grey-2': !$q.dark.isActive,
|
||||
}"
|
||||
>
|
||||
<div v-if="loading" class="user-list q-pa-md flex justify-center">
|
||||
<q-spinner color="primary" size="3em" :thickness="10" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="Object.keys(video).length" class="user-list q-pa-md">
|
||||
<div class="row q-pa-sm">
|
||||
<span class="text-center text-h6">Vídeo</span>
|
||||
</div>
|
||||
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-12">
|
||||
<DisplayValue label="Título" :value="video.title" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-3">
|
||||
<DisplayValue label="Video ID" :value="video.id" />
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<DisplayValue label="Duração" :value="getDuration()" />
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<DisplayValue label="Data de Postagem" :value="getDateVideo()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="user-list q-pa-md">
|
||||
<p class="text-center">
|
||||
Cole uma URL e faça a busca dos dados do vídeo
|
||||
</p>
|
||||
</div>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
|
||||
import Button from "@components/Button";
|
||||
import TextField from "@components/TextField";
|
||||
import DisplayValue from "@components/DisplayValue";
|
||||
|
||||
import { API } from "@config/axios";
|
||||
import { getErrorMessage } from "@utils/axios";
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
const mock = {
|
||||
id: "x9-YRAYhesI",
|
||||
title:
|
||||
"PROVEI A COMIDA DO EXÉRCITO CHINÊS - TROPA DE ELITE DA CHINA, PACOTE RARO",
|
||||
thumbnail: "https://i.ytimg.com/vi_webp/x9-YRAYhesI/maxresdefault.webp",
|
||||
description:
|
||||
"Hoje iremos experimentar o mais novo pacote de ração militar das forças de elite da China, um dos pacotes de MRE mais difíceis e raros! Vamos comparar para ver se esse pacote é superior ao do exército do Brasil!?",
|
||||
channel_id: "UCcFgREmujdPHvA7I_VOmgIA",
|
||||
channel_url: "https://www.youtube.com/channel/UCcFgREmujdPHvA7I_VOmgIA",
|
||||
duration: 1534,
|
||||
view_count: 352973,
|
||||
webpage_url: "https://www.youtube.com/watch?v=x9-YRAYhesI",
|
||||
categories: ["People & Blogs"],
|
||||
tags: [
|
||||
"area secreta",
|
||||
"mre",
|
||||
"camping",
|
||||
"balian",
|
||||
"acampamento",
|
||||
"comida do exército",
|
||||
"ração militar",
|
||||
"sobrevivi",
|
||||
"24 horas",
|
||||
"acampando",
|
||||
"comida chinesa",
|
||||
],
|
||||
comment_count: 785,
|
||||
like_count: 26175,
|
||||
channel: "Área Secreta",
|
||||
channel_follower_count: 10700000,
|
||||
uploader: "Área Secreta",
|
||||
timestamp: 1740956718,
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "NewVideo",
|
||||
components: {
|
||||
Button,
|
||||
TextField,
|
||||
DisplayValue,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: "",
|
||||
loading: false,
|
||||
video: mock,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async handleSearch() {
|
||||
if (!this.url) return;
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
const { data } = await API.get("/videos/search", {
|
||||
params: {
|
||||
url: this.url,
|
||||
},
|
||||
});
|
||||
|
||||
this.video = data;
|
||||
} catch (error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
message: getErrorMessage(error, "Erro ao buscar vídeos"),
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
getDateVideo() {
|
||||
const duration = dayjs(this.video.timestamp * 1000);
|
||||
|
||||
return duration.format("DD/MM/YYYY HH:mm:ss");
|
||||
},
|
||||
getDuration() {
|
||||
const duration = dayjs.duration(this.video.duration, "seconds");
|
||||
|
||||
return duration.format("HH:mm:ss");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-list {
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user