Ajusta layout
This commit is contained in:
@@ -1,68 +1,123 @@
|
||||
<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 q-gutter-md">
|
||||
<div class="col-3">
|
||||
<TextField label="Título" />
|
||||
<div class="videos-page">
|
||||
<section class="app-page__header">
|
||||
<div>
|
||||
<h2 class="app-page__title">Biblioteca de vídeos</h2>
|
||||
<p class="app-page__subtitle">
|
||||
Filtre os vídeos processados pela IA, acompanhe o status dos recortes e inicie novas captações
|
||||
em poucos cliques.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="app-actions-bar">
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="sym_o_refresh"
|
||||
label="Atualizar"
|
||||
@click="handleSearch(pagination)"
|
||||
/>
|
||||
<Button icon="sym_o_add" label="Novo vídeo" @click="handleAddVideo" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<q-card flat bordered class="videos-page__filters app-card">
|
||||
<div class="videos-page__filters-head">
|
||||
<div>
|
||||
<h3>Filtrar resultados</h3>
|
||||
<span>Refine a busca por título, identificador ou situação.</span>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<TextField label="Video ID" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<Dropdown
|
||||
v-model="select"
|
||||
label="Situação"
|
||||
clearable
|
||||
:options="situations"
|
||||
:loading="situationsLoading"
|
||||
|
||||
<div class="videos-page__filters-actions">
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
text-color="primary"
|
||||
icon="sym_o_close"
|
||||
label="Limpar filtros"
|
||||
:disabled="!hasFilters"
|
||||
@click="resetFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-6">
|
||||
<Button
|
||||
label="Buscar"
|
||||
:loading="loading"
|
||||
@click="handleSearch(pagination)"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<Button label="Teste" @click="handleTeste()" fullWidth />
|
||||
</div>
|
||||
<div class="videos-page__filters-grid">
|
||||
<TextField
|
||||
v-model="filters.title"
|
||||
label="Título"
|
||||
placeholder="Ex.: Cortes da live com convidados"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon name="sym_o_title" color="primary" />
|
||||
</template>
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
v-model="filters.videoId"
|
||||
label="Video ID"
|
||||
placeholder="Ex.: x9-YRAYhesI"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon name="sym_o_confirmation_number" color="primary" />
|
||||
</template>
|
||||
</TextField>
|
||||
|
||||
<Dropdown
|
||||
v-model="filters.situations"
|
||||
label="Situação"
|
||||
clearable
|
||||
:options="situations"
|
||||
:loading="situationsLoading"
|
||||
multiple
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="videos-page__filters-footer">
|
||||
<Button
|
||||
label="Buscar vídeos"
|
||||
icon="sym_o_search"
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
@click="handleSearch({ ...pagination, page: 1 })"
|
||||
/>
|
||||
</div>
|
||||
</q-card>
|
||||
|
||||
<Table
|
||||
key="videos-table"
|
||||
class="videos-page__table"
|
||||
title="Resultados"
|
||||
subtitle="Veja os vídeos cadastrados, a quantidade de clipes e o status de processamento."
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
key="table-videos"
|
||||
@update:pagination="updatePagination"
|
||||
>
|
||||
<template #no-data>
|
||||
<div
|
||||
class="full-width row flex-center q-gutter-sm"
|
||||
style="font-size: 1.3em"
|
||||
>
|
||||
<span> Não há vídeos </span>
|
||||
</div>
|
||||
<template #actions>
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="sym_o_download"
|
||||
label="Exportar CSV"
|
||||
@click="handleExport"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #empty-message>
|
||||
Nenhum vídeo encontrado para os filtros selecionados.
|
||||
</template>
|
||||
|
||||
<template #body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<div class="videos-page__cell">
|
||||
<div class="videos-page__cell-label">{{ col.label }}</div>
|
||||
<div class="videos-page__cell-value">{{ col.value }}</div>
|
||||
</div>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</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>
|
||||
@@ -84,18 +139,18 @@ const columns = [
|
||||
{
|
||||
name: "title",
|
||||
label: "Título",
|
||||
align: "center",
|
||||
align: "left",
|
||||
field: "title",
|
||||
},
|
||||
{
|
||||
name: "clips_quantity",
|
||||
label: "Clipes",
|
||||
align: "left",
|
||||
align: "center",
|
||||
field: "clips_quantity",
|
||||
},
|
||||
{
|
||||
name: "videoid",
|
||||
label: "VideoID",
|
||||
label: "Video ID",
|
||||
field: "videoid",
|
||||
},
|
||||
{
|
||||
@@ -106,7 +161,7 @@ const columns = [
|
||||
];
|
||||
|
||||
export default {
|
||||
name: "UserList",
|
||||
name: "VideosList",
|
||||
components: {
|
||||
Button,
|
||||
Table,
|
||||
@@ -122,27 +177,54 @@ export default {
|
||||
page: 1,
|
||||
direction: "desc",
|
||||
perPage: 10,
|
||||
total: 10,
|
||||
total: 0,
|
||||
totalPages: 1,
|
||||
hasNext: false,
|
||||
hasPrev: false,
|
||||
},
|
||||
situations: [],
|
||||
situationsLoading: false,
|
||||
select: [],
|
||||
filters: {
|
||||
title: "",
|
||||
videoId: "",
|
||||
situations: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getSituation();
|
||||
computed: {
|
||||
hasFilters() {
|
||||
return (
|
||||
!!this.filters.title ||
|
||||
!!this.filters.videoId ||
|
||||
(Array.isArray(this.filters.situations) && this.filters.situations.length > 0)
|
||||
);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initializePage();
|
||||
},
|
||||
methods: {
|
||||
async initializePage() {
|
||||
await this.getSituation();
|
||||
await this.handleSearch(this.pagination);
|
||||
},
|
||||
async handleSearch(pagination) {
|
||||
try {
|
||||
this.loading = true;
|
||||
|
||||
const baseParams = {
|
||||
situation: this.select,
|
||||
};
|
||||
const baseParams = {};
|
||||
|
||||
if (this.filters.title) {
|
||||
baseParams.title = this.filters.title;
|
||||
}
|
||||
|
||||
if (this.filters.videoId) {
|
||||
baseParams.videoId = this.filters.videoId;
|
||||
}
|
||||
|
||||
if (Array.isArray(this.filters.situations) && this.filters.situations.length) {
|
||||
baseParams.situation = this.filters.situations;
|
||||
}
|
||||
|
||||
const { data } = await API.get("/videos", {
|
||||
params: {
|
||||
@@ -182,31 +264,138 @@ export default {
|
||||
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");
|
||||
},
|
||||
resetFilters() {
|
||||
this.filters = {
|
||||
title: "",
|
||||
videoId: "",
|
||||
situations: [],
|
||||
};
|
||||
|
||||
this.handleSearch({ ...this.pagination, page: 1 });
|
||||
},
|
||||
handleExport() {
|
||||
if (!this.rows.length) {
|
||||
this.$q.notify({
|
||||
type: "warning",
|
||||
message: "Não há dados para exportar. Ajuste os filtros e tente novamente.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.$q.notify({
|
||||
type: "info",
|
||||
message: "Exportação em CSV disponível em breve.",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-list {
|
||||
margin: 0 auto;
|
||||
<style scoped lang="scss">
|
||||
.videos-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.videos-page__filters h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.videos-page__filters span {
|
||||
color: #475467;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.videos-page__filters-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.videos-page__filters-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.videos-page__filters-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.videos-page__filters-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.videos-page__table {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.videos-page__cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.videos-page__cell-label {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #98a2b3;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.videos-page__cell-value {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
body.body--dark .videos-page__filters h3 {
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
body.body--dark .videos-page__filters span {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
body.body--dark .videos-page__cell-value {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.videos-page__filters-head {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.app-actions-bar {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.videos-page__filters-footer {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.videos-page__filters-footer > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.videos-page__cell-label {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,183 +1,244 @@
|
||||
<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 class="video-create">
|
||||
<section class="app-page__header">
|
||||
<div>
|
||||
<h2 class="app-page__title">Cadastrar novo vídeo</h2>
|
||||
<p class="app-page__subtitle">
|
||||
Cole a URL do YouTube para que a IA analise o conteúdo, identifique os
|
||||
picos de atenção e gere sugestões de clipes automaticamente.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-6">
|
||||
<div class="app-actions-bar">
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
text-color="primary"
|
||||
icon="sym_o_lightbulb"
|
||||
label="Guia de cortes rápidos"
|
||||
@click="handleGuide"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="video-create__grid">
|
||||
<q-card flat bordered class="app-card video-create__card">
|
||||
<header class="video-create__card-head">
|
||||
<div>
|
||||
<h3>URL do vídeo</h3>
|
||||
<p>
|
||||
Utilize vídeos públicos ou não listados. Links privados não são
|
||||
suportados.
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<TextField
|
||||
v-model="url"
|
||||
label="Link do YouTube"
|
||||
placeholder="https://www.youtube.com/watch?v=..."
|
||||
:disabled="loading"
|
||||
required
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon name="sym_o_link" color="primary" />
|
||||
</template>
|
||||
<template #append>
|
||||
<q-btn
|
||||
v-if="url"
|
||||
dense
|
||||
flat
|
||||
round
|
||||
icon="sym_o_close"
|
||||
color="primary"
|
||||
@click="url = ''"
|
||||
/>
|
||||
</template>
|
||||
</TextField>
|
||||
|
||||
<div class="video-create__actions">
|
||||
<Button
|
||||
label="Buscar Informações"
|
||||
label="Buscar informações"
|
||||
icon="sym_o_search"
|
||||
:loading="loading"
|
||||
: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>
|
||||
<ul class="video-create__hints">
|
||||
<li>Informe apenas o link completo do vídeo no YouTube.</li>
|
||||
<li>
|
||||
Suporte a vídeos até 4 horas e canais com integração liberada.
|
||||
</li>
|
||||
<li>Após a busca, confirme os dados e envie para processamento.</li>
|
||||
</ul>
|
||||
</q-card>
|
||||
|
||||
<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-12">
|
||||
<DisplayValue label="Descrição" :value="video.description" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-4">
|
||||
<DisplayValue label="Video ID" :value="video.id" />
|
||||
<transition name="fade-slide">
|
||||
<q-card
|
||||
v-if="loading || videoLoaded"
|
||||
flat
|
||||
bordered
|
||||
class="video-create__preview-card"
|
||||
>
|
||||
<div v-if="loading" class="video-create__preview-loading">
|
||||
<q-skeleton type="rect" class="video-create__thumbnail-skeleton" />
|
||||
<q-skeleton type="text" width="80%" />
|
||||
<q-skeleton type="text" width="60%" />
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<DisplayValue label="Duração" :value="getDuration()" />
|
||||
</div>
|
||||
<div v-else class="video-create__preview">
|
||||
<div class="video-create__thumbnail">
|
||||
<img :src="videoThumbnail" :alt="video.title" />
|
||||
<q-badge color="primary" class="video-create__badge">{{
|
||||
formattedDuration
|
||||
}}</q-badge>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<DisplayValue label="Data de Postagem" :value="getDateVideo()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-4">
|
||||
<DisplayValue
|
||||
label="Visualizações"
|
||||
:value="convertoToNumberFormat(video.view_count)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<DisplayValue
|
||||
label="Likes"
|
||||
:value="convertoToNumberFormat(video.like_count)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<DisplayValue
|
||||
label="Comentários"
|
||||
:value="convertoToNumberFormat(video.comment_count)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-6">
|
||||
<DisplayValue
|
||||
label="Categorias"
|
||||
:value="video.categories.join(', ')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 q-mt-md">
|
||||
<div style="color: #999; font-weight: bold">Tags</div>
|
||||
|
||||
<div class="row q-mt-sm">
|
||||
<div v-for="tag in video.tags" :key="tag">
|
||||
<q-chip color="primary">{{ tag }}</q-chip>
|
||||
<div class="video-create__preview-body">
|
||||
<h4>{{ video.title }}</h4>
|
||||
<p>{{ video.description }}</p>
|
||||
<div class="video-create__preview-actions">
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="sym_o_content_copy"
|
||||
label="Copiar ID do vídeo"
|
||||
@click="
|
||||
copyToClipboard(
|
||||
video.id,
|
||||
'Video ID copiado para a área de transferência.'
|
||||
)
|
||||
"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
text-color="primary"
|
||||
icon="sym_o_open_in_new"
|
||||
label="Abrir no YouTube"
|
||||
@click="openOnYoutube"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<q-separator class="q-mt-md" />
|
||||
<transition name="fade-slide">
|
||||
<div v-if="videoLoaded" class="video-create__details">
|
||||
<q-card flat bordered class="app-card video-create__section">
|
||||
<header class="video-create__section-head">
|
||||
<div>
|
||||
<h3>Insights do vídeo</h3>
|
||||
<span>Resumo dos dados coletados diretamente da plataforma.</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="sym_o_download"
|
||||
label="Exportar dados"
|
||||
@click="handleExport"
|
||||
/>
|
||||
</header>
|
||||
|
||||
<div class="row q-mt-md q-pa-sm">
|
||||
<span class="text-center text-h6">Canal</span>
|
||||
</div>
|
||||
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-4">
|
||||
<q-tooltip :offset="[-200, 0]">
|
||||
Clique para abrir no youtube
|
||||
</q-tooltip>
|
||||
<a :href="video.channel_url" target="_blank">
|
||||
<DisplayValue label="Canal" :value="video.channel" />
|
||||
</a>
|
||||
<div class="video-create__stats">
|
||||
<DisplayValue
|
||||
label="Visualizações"
|
||||
:value="formatNumber(video.view_count)"
|
||||
/>
|
||||
<DisplayValue
|
||||
label="Likes"
|
||||
:value="formatNumber(video.like_count)"
|
||||
/>
|
||||
<DisplayValue
|
||||
label="Comentários"
|
||||
:value="formatNumber(video.comment_count)"
|
||||
/>
|
||||
<DisplayValue label="Postado em" :value="formattedDate" />
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<div class="video-create__meta">
|
||||
<DisplayValue
|
||||
label="Inscritos"
|
||||
:value="convertoToNumberFormat(video.channel_follower_count)"
|
||||
label="Categorias"
|
||||
:value="
|
||||
video.categories?.length
|
||||
? video.categories.join(', ')
|
||||
: 'Não informado'
|
||||
"
|
||||
/>
|
||||
<DisplayValue
|
||||
label="Tags"
|
||||
:value="
|
||||
video.tags?.length
|
||||
? `${video.tags.length} tags detectadas`
|
||||
: 'Nenhuma tag localizada'
|
||||
"
|
||||
helper="Os melhores recortes consideram tags com termos fortes."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<DisplayValue label="Identificador" :value="video.channel_id" />
|
||||
<div v-if="video.tags?.length" class="video-create__tags">
|
||||
<q-chip
|
||||
v-for="tag in video.tags"
|
||||
:key="tag"
|
||||
color="primary"
|
||||
text-color="white"
|
||||
>
|
||||
{{ tag }}
|
||||
</q-chip>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
|
||||
<q-separator v-if="video.thumbnail" class="q-mt-md" />
|
||||
<q-card flat bordered class="app-card video-create__section">
|
||||
<header class="video-create__section-head">
|
||||
<div>
|
||||
<h3>Canal</h3>
|
||||
<span
|
||||
>Entenda a audiência do canal para calibrar os próximos
|
||||
cortes.</span
|
||||
>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div v-if="video.thumbnail" class="row q-mt-md q-pa-sm">
|
||||
<span class="text-center text-h6">Thumbnail</span>
|
||||
</div>
|
||||
<div class="video-create__meta">
|
||||
<DisplayValue label="Nome" :value="video.channel" />
|
||||
<DisplayValue
|
||||
label="Inscritos"
|
||||
:value="
|
||||
video.channel_follower_count
|
||||
? formatNumber(video.channel_follower_count)
|
||||
: '—'
|
||||
"
|
||||
/>
|
||||
<DisplayValue
|
||||
label="Uploader"
|
||||
:value="video.uploader || video.channel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="video.thumbnail" class="row q-pa-sm">
|
||||
<img
|
||||
:src="video.thumbnail"
|
||||
alt="Thumbnail"
|
||||
height="300"
|
||||
width="500"
|
||||
/>
|
||||
</div>
|
||||
<div class="video-create__actions">
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
text-color="primary"
|
||||
icon="sym_o_notifications_active"
|
||||
label="Ativar alertas do canal"
|
||||
@click="handleChannelAlerts"
|
||||
/>
|
||||
|
||||
<Button
|
||||
label="Enviar para processamento"
|
||||
icon="sym_o_auto_awesome"
|
||||
:disabled="!video.id"
|
||||
:loading="saveLoading"
|
||||
@click="handleSave"
|
||||
/>
|
||||
</div>
|
||||
</q-card>
|
||||
</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>
|
||||
|
||||
<q-card flat bordered>
|
||||
<div class="row justify-between q-pa-sm">
|
||||
<div>
|
||||
<Button label="Voltar" color="negative" @click="handleCancel" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
label="Cadastrar"
|
||||
@click="handleSave"
|
||||
:loading="saveLoading"
|
||||
:disabled="!video.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -194,40 +255,6 @@ 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: {
|
||||
@@ -239,19 +266,52 @@ export default {
|
||||
return {
|
||||
url: "",
|
||||
loading: false,
|
||||
saveLoading: false,
|
||||
video: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
videoLoaded() {
|
||||
return Object.keys(this.video).length > 0;
|
||||
},
|
||||
videoThumbnail() {
|
||||
if (this.video.thumbnail) return this.video.thumbnail;
|
||||
if (
|
||||
Array.isArray(this.video.thumbnails) &&
|
||||
this.video.thumbnails.length
|
||||
) {
|
||||
return this.video.thumbnails[0];
|
||||
}
|
||||
|
||||
if (this.video.id) {
|
||||
return `https://i.ytimg.com/vi/${this.video.id}/hqdefault.jpg`;
|
||||
}
|
||||
|
||||
return "https://placehold.co/640x360/7f56d9/FFFFFF?text=Clipper+IA";
|
||||
},
|
||||
formattedDuration() {
|
||||
if (!this.video.duration) return "—";
|
||||
|
||||
const videoDuration = dayjs.duration(this.video.duration, "seconds");
|
||||
return videoDuration.format("HH:mm:ss");
|
||||
},
|
||||
formattedDate() {
|
||||
if (!this.video.timestamp) return "—";
|
||||
|
||||
return dayjs(this.video.timestamp * 1000).format("DD/MM/YYYY HH:mm");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async handleSearch() {
|
||||
if (!this.url) return;
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
this.video = {};
|
||||
|
||||
const { data } = await API.get("/videos/search", {
|
||||
params: {
|
||||
url: this.url,
|
||||
url: this.url.trim(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -259,40 +319,324 @@ export default {
|
||||
} catch (error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
message: getErrorMessage(error, "Erro ao buscar vídeos"),
|
||||
message: getErrorMessage(
|
||||
error,
|
||||
"Erro ao buscar informações do vídeo"
|
||||
),
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
getDateVideo() {
|
||||
const duration = dayjs(this.video.timestamp * 1000);
|
||||
formatNumber(number) {
|
||||
if (!number && number !== 0) return "—";
|
||||
|
||||
return duration.format("DD/MM/YYYY HH:mm:ss");
|
||||
},
|
||||
getDuration() {
|
||||
const duration = dayjs.duration(this.video.duration, "seconds");
|
||||
|
||||
return duration.format("HH:mm:ss");
|
||||
},
|
||||
convertoToNumberFormat(number) {
|
||||
const formatter = new Intl.NumberFormat("pt-BR", {
|
||||
maximumSignificantDigits: 3,
|
||||
notation: "compact",
|
||||
compactDisplay: "short",
|
||||
});
|
||||
|
||||
return formatter.format(number);
|
||||
},
|
||||
copyToClipboard(value, successMessage) {
|
||||
if (!value) return;
|
||||
|
||||
const canUseClipboard =
|
||||
typeof navigator !== "undefined" && navigator.clipboard;
|
||||
|
||||
if (canUseClipboard) {
|
||||
navigator.clipboard
|
||||
.writeText(value)
|
||||
.then(() => {
|
||||
this.$q.notify({ type: "positive", message: successMessage });
|
||||
})
|
||||
.catch(() => {
|
||||
this.$q.notify({
|
||||
type: "warning",
|
||||
message: "Não foi possível copiar o conteúdo.",
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.$q.notify({
|
||||
type: "warning",
|
||||
message: "Clipboard não suportado neste dispositivo.",
|
||||
});
|
||||
}
|
||||
},
|
||||
openOnYoutube() {
|
||||
if (!this.video.webpage_url || typeof window === "undefined") return;
|
||||
window.open(this.video.webpage_url, "_blank", "noopener");
|
||||
},
|
||||
handleExport() {
|
||||
this.$q.notify({
|
||||
type: "info",
|
||||
message: "Exportação disponível em breve.",
|
||||
});
|
||||
},
|
||||
handleGuide() {
|
||||
this.$q.notify({
|
||||
type: "info",
|
||||
message:
|
||||
"Confira em breve nosso guia com as melhores práticas de cortes!",
|
||||
});
|
||||
},
|
||||
handleChannelAlerts() {
|
||||
this.$q.notify({
|
||||
type: "info",
|
||||
message:
|
||||
"Integração com alertas do canal estará disponível em uma próxima atualização.",
|
||||
});
|
||||
},
|
||||
async handleSave() {
|
||||
if (!this.video.id) {
|
||||
this.$q.notify({
|
||||
type: "warning",
|
||||
message: "Busque um vídeo antes de enviar para processamento.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.saveLoading = true;
|
||||
// Chamada de criação ainda não implementada.
|
||||
await new Promise((resolve) => setTimeout(resolve, 1200));
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
message:
|
||||
"Vídeo enviado para processamento. Você será notificado quando os cortes estiverem prontos!",
|
||||
});
|
||||
} catch (error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
message:
|
||||
"Não foi possível enviar o vídeo. Tente novamente mais tarde.",
|
||||
});
|
||||
} finally {
|
||||
this.saveLoading = false;
|
||||
}
|
||||
},
|
||||
pasteExample() {
|
||||
this.url = "https://www.youtube.com/watch?v=x9-YRAYhesI";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-list {
|
||||
margin: 0 auto;
|
||||
<style scoped lang="scss">
|
||||
.video-create {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: clamp(32px, 6vw, 48px);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
.video-create__grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 420px);
|
||||
gap: clamp(24px, 4vw, 36px);
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.video-create__card h3 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.video-create__card p {
|
||||
margin: 4px 0 0;
|
||||
color: #475467;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.video-create__card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.video-create__actions {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.video-create__hints {
|
||||
margin: 24px 0 0;
|
||||
padding: 0 0 0 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
color: #475467;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.video-create__preview-card {
|
||||
border-radius: var(--app-border-radius);
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-create__preview-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.video-create__thumbnail-skeleton {
|
||||
height: 180px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.video-create__preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.video-create__thumbnail {
|
||||
position: relative;
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 16 / 9;
|
||||
background: rgba(127, 86, 217, 0.12);
|
||||
}
|
||||
|
||||
.video-create__thumbnail img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-create__badge {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
border-radius: 999px;
|
||||
padding: 6px 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.video-create__preview-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.video-create__preview-body h4 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.video-create__preview-body p {
|
||||
margin: 0;
|
||||
color: #475467;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.video-create__preview-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.video-create__details {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: clamp(24px, 4vw, 36px);
|
||||
}
|
||||
|
||||
.video-create__section-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.video-create__section-head h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.video-create__section-head span {
|
||||
font-size: 14px;
|
||||
color: #475467;
|
||||
}
|
||||
|
||||
.video-create__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.video-create__meta {
|
||||
margin-top: 24px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.video-create__tags {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.video-create__actions {
|
||||
margin-top: 28px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.fade-slide-enter-active,
|
||||
.fade-slide-leave-active {
|
||||
transition: opacity 0.22s ease, transform 0.22s ease;
|
||||
}
|
||||
|
||||
.fade-slide-enter-from,
|
||||
.fade-slide-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
|
||||
body.body--dark .video-create__card h3,
|
||||
body.body--dark .video-create__preview-body h4,
|
||||
body.body--dark .video-create__section-head h3 {
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
body.body--dark .video-create__card p,
|
||||
body.body--dark .video-create__preview-body p,
|
||||
body.body--dark .video-create__section-head span,
|
||||
body.body--dark .video-create__hints,
|
||||
body.body--dark .video-create__actions {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.video-create__grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.video-create__preview-actions,
|
||||
.video-create__actions {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user