Files
DLE/frontend/src/views/content/TemplatesListView.vue
2025-10-23 13:53:44 +03:00

152 lines
6.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
Copyright (c) 2024-2025 Тарабанов Александр Викторович
All rights reserved.
-->
<template>
<BaseLayout :is-authenticated="isAuthenticated" :identities="identities" :token-balances="tokenBalances" :is-loading-tokens="isLoadingTokens" @auth-action-completed="$emit('auth-action-completed')">
<div class="list-page">
<div class="page-header">
<div class="header-content">
<h1>Шаблоны документов</h1>
<p>Системные шаблоны для персонализации и публикации.</p>
</div>
<div class="header-actions">
<button class="close-btn" @click="goBack">×</button>
</div>
</div>
<div class="content-block">
<div class="section-header">
<h2>Список шаблонов</h2>
<div class="filters">
<div class="filter-group">
<label for="visibility-filter">Видимость:</label>
<select v-model="visibilityFilter" id="visibility-filter" class="filter-select">
<option value="">Все</option>
<option value="public">Публичные</option>
<option value="internal">Внутренние</option>
</select>
</div>
<div class="search-box">
<input v-model="search" type="text" placeholder="Поиск шаблонов..." class="search-input" />
<i class="fas fa-search search-icon"></i>
</div>
</div>
</div>
<div v-if="filtered.length" class="pages-grid">
<div v-for="p in filtered" :key="p.id" class="page-card" @click="open(p.id)">
<div class="page-card-header">
<h3>{{ p.title }}</h3>
</div>
<div class="page-card-content">
<p class="page-summary">{{ p.summary || 'Без описания' }}</p>
<div class="page-meta">
<span class="page-status draft"><i class="fas fa-circle"></i>Черновик</span>
<span class="page-status"><i class="fas fa-cube"></i>Шаблон</span>
<span class="page-status" :class="p.visibility"><i class="fas fa-eye"></i>{{ p.visibility === 'internal' ? 'Внутренний' : 'Публичный' }}</span>
</div>
</div>
</div>
</div>
<div v-else class="empty-state">
<div class="empty-icon"><i class="fas fa-file-alt"></i></div>
<h3>Шаблонов не найдено</h3>
<p v-if="!canEditData || !address">Требуются права редактора и подключённый кошелёк для просмотра системных шаблонов.</p>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import pagesService from '../../services/pagesService';
import { useAuthContext } from '../../composables/useAuth';
import { usePermissions } from '../../composables/usePermissions';
const props = defineProps({
isAuthenticated: { type: Boolean, default: false },
identities: { type: Array, default: () => [] },
tokenBalances: { type: Object, default: () => ({}) },
isLoadingTokens: { type: Boolean, default: false },
});
const router = useRouter();
const search = ref('');
const visibilityFilter = ref('');
const pages = ref([]);
const { address } = useAuthContext();
const { canEditData } = usePermissions();
function goBack() { router.push({ name: 'content-list' }); }
function open(id) {
if (canEditData.value && address.value) {
router.push({ name: 'page-view', params: { id } });
} else {
router.push({ name: 'public-page-view', params: { id } });
}
}
const filtered = computed(() => {
return pages.value.filter(p =>
(p.is_system_template === true) &&
(p.visibility === 'public' || p.visibility === 'internal') &&
(!visibilityFilter.value || p.visibility === visibilityFilter.value) &&
(!search.value || p.title?.toLowerCase().includes(search.value.toLowerCase()))
);
});
onMounted(async () => {
try {
if (canEditData.value && address.value) {
try {
pages.value = await pagesService.getPages();
} catch (e) {
pages.value = [];
}
} else {
pages.value = [];
}
} catch (e) {
pages.value = [];
}
});
</script>
<style scoped>
.list-page { padding: 20px; width: 100%; }
.page-header { display:flex; justify-content: space-between; align-items: flex-start; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #f0f0f0; }
.header-content h1 { color: var(--color-primary); font-size: 2.2rem; margin: 0 0 8px 0; }
.header-content p { color: var(--color-grey-dark); margin: 0; }
.close-btn { background:none; border:none; font-size: 1.5rem; cursor:pointer; color:#888; }
/* Переиспользуем стили из ContentListView */
.content-block { background: #f8f9fa; border-radius: var(--radius-lg); padding: 25px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.section-header { display:flex; justify-content: space-between; align-items:center; margin-bottom: 20px; }
.section-header h2 { color: var(--color-primary); margin: 0; }
.filters { display: flex; gap: 20px; align-items: center; }
.filter-group { display: flex; align-items: center; gap: 8px; }
.filter-group label { color: var(--color-grey-dark); font-weight: 500; }
.filter-select { padding: 8px 12px; border: 1px solid #e9ecef; border-radius: var(--radius-sm); background: #fff; font-size: 1rem; }
.search-box { position: relative; width: 300px; }
.search-input { width: 100%; padding: 10px 40px 10px 15px; border: 1px solid #e9ecef; border-radius: var(--radius-sm); font-size: 1rem; }
.search-icon { position: absolute; right: 15px; top: 50%; transform: translateY(-50%); color: var(--color-grey-dark); }
.pages-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; margin-top: 10px; }
.page-card { background: #fff; border: 1px solid #e9ecef; border-radius: var(--radius-sm); padding: 16px; cursor: pointer; transition: all 0.2s; }
.page-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); transform: translateY(-2px); }
.page-card-header h3 { margin: 0; color: var(--color-primary); font-size: 1.2rem; }
.page-summary { color: var(--color-grey-dark); margin: 8px 0 12px; }
.page-meta { display:flex; gap: 12px; font-size: 0.9rem; color: var(--color-grey-dark); align-items: center; flex-wrap: wrap; }
.page-status i { margin-right: 6px; }
.page-status.draft i { color: #ff9800; }
.empty-state { text-align:center; padding: 60px 20px; }
.empty-icon { font-size: 3rem; color: var(--color-grey-dark); margin-bottom: 10px; }
</style>