Files
Beyhan Oğur 5285a0dd86 first commit
2026-04-26 22:07:47 +03:00

254 lines
12 KiB
Vue
Raw Permalink 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.
<template>
<div>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Yeni Gönderi Ekle</h2>
<NuxtLink to="/admin/blog/posts" class="btn btn-secondary">
<i class="fas fa-arrow-left me-2"></i> Geri Dön
</NuxtLink>
</div>
<div class="card">
<div class="card-body">
<form @submit.prevent="savePost">
<div class="row">
<div class="col-md-8">
<!-- Title -->
<div class="mb-3">
<label class="form-label">Başlık</label>
<input v-model="formData.title" type="text" class="form-control" required>
</div>
<!-- Content -->
<div class="mb-3">
<label class="form-label">İçerik</label>
<textarea v-model="formData.content" class="form-control" rows="10" required></textarea>
<div class="form-text">HTML içeriği girebilirsiniz.</div>
</div>
<!-- Tags -->
<div class="card mb-3">
<div class="card-header">Etiketler</div>
<div class="card-body" style="max-height: 200px; overflow-y: auto;">
<div v-if="availableTags.length === 0" class="text-muted small">Etiket bulunamadı.</div>
<div v-for="tag in availableTags" :key="tag.ID" class="form-check">
<input class="form-check-input" type="checkbox" :value="tag.ID" v-model="selectedTags" :id="`tag-${tag.ID}`">
<label class="form-check-label" :for="`tag-${tag.ID}`">
{{ tag.name }}
</label>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<!-- Categories -->
<div class="card mb-3">
<div class="card-header">Kategoriler</div>
<div class="card-body" style="max-height: 300px; overflow-y: auto;">
<div v-if="categories.length === 0" class="text-muted small">Kategori bulunamadı.</div>
<div v-for="cat in categories" :key="cat.ID" class="form-check">
<input class="form-check-input" type="checkbox" :value="cat.ID" v-model="formData.category_ids" :id="`cat-${cat.ID}`">
<label class="form-check-label" :for="`cat-${cat.ID}`">
{{ cat.title }}
</label>
</div>
</div>
</div>
<!-- Image Upload & Options -->
<div class="card mb-3">
<div class="card-header">Öne Çıkan Görsel</div>
<div class="card-body">
<div class="mb-3 text-center">
<div v-if="imagePreview" class="mb-3">
<img :src="imagePreview" class="img-fluid rounded" style="max-height: 200px;">
<button type="button" class="btn btn-sm btn-danger mt-2" @click="removeImage">Kaldır</button>
</div>
<div v-else>
<div class="mb-3">
<i class="fas fa-image fa-3x text-muted"></i>
</div>
<input type="file" class="form-control" @change="handleFileUpload" accept="image/*">
</div>
</div>
<div v-if="imageFile">
<hr>
<h6>Resim Ayarları</h6>
<div class="row g-2">
<div class="col-6">
<label class="form-label small">Genişlik (px)</label>
<input v-model="imageOptions.width" type="number" class="form-control form-control-sm">
</div>
<div class="col-6">
<label class="form-label small">Yükseklik (px)</label>
<input v-model="imageOptions.height" type="number" class="form-control form-control-sm" placeholder="Opsiyonel">
</div>
<div class="col-6">
<label class="form-label small">Kalite (1-100)</label>
<input v-model="imageOptions.quality" type="number" class="form-control form-control-sm" min="1" max="100">
</div>
<div class="col-6">
<label class="form-label small">Format</label>
<select v-model="imageOptions.format" class="form-select form-select-sm">
<option value="jpeg">JPEG</option>
<option value="png">PNG</option>
<option value="webp">WebP</option>
<option value="avif">AVIF</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Publish Button -->
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary" :disabled="loading">
<span v-if="loading" class="spinner-border spinner-border-sm me-2"></span>
{{ loading ? 'İşleniyor...' : 'Yayınla' }}
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { Category } from '~~/types/category';
import type { Tag, TagResponse } from '~~/types/tag';
import Swal from 'sweetalert2';
definePageMeta({
layout: 'admin',
middleware: 'admin'
});
const config = useRuntimeConfig();
const { data: authData } = useAuth();
const router = useRouter();
const loading = ref(false);
const categories = ref<Category[]>([]);
const availableTags = ref<Tag[]>([]);
const selectedTags = ref<number[]>([]);
const imageFile = ref<File | null>(null);
const imagePreview = ref<string | null>(null);
const formData = ref({
title: '',
content: '',
category_ids: [] as number[],
});
const imageOptions = ref({
width: null as number | null,
height: null as number | null,
quality: 80,
format: 'jpeg'
});
// Fetch Categories and Tags
const fetchData = async () => {
try {
const [catRes, tagRes] = await Promise.all([
$fetch<Category[]>('/api/v1/categories', {
baseURL: config.public.BASE_API_URL,
headers: { Authorization: `Bearer ${(authData.value as any)?.accessToken}` }
}),
$fetch<TagResponse>('/api/v1/admin/tags', { // Fetching all tags (admin endpoint for safety/completeness)
baseURL: config.public.BASE_API_URL,
headers: { Authorization: `Bearer ${(authData.value as any)?.accessToken}` },
params: { limit: 100 } // Get a reasonable amount of tags
})
]);
categories.value = catRes || [];
availableTags.value = tagRes?.data || [];
} catch (e) {
console.error(e);
}
};
const handleFileUpload = (event: Event) => {
const target = event.target as HTMLInputElement;
if (target.files && target.files[0]) {
const file = target.files[0];
imageFile.value = file;
imagePreview.value = URL.createObjectURL(file);
// Default format based on uploaded file or keep jpeg
if (file.type === 'image/png') imageOptions.value.format = 'png';
else if (file.type === 'image/webp') imageOptions.value.format = 'webp';
else imageOptions.value.format = 'jpeg';
}
};
const removeImage = () => {
imageFile.value = null;
imagePreview.value = null;
};
const savePost = async () => {
loading.value = true;
try {
const data = new FormData();
data.append('title', formData.value.title);
data.append('content', formData.value.content);
if (formData.value.category_ids.length > 0) {
data.append('category_ids', formData.value.category_ids.join(','));
}
if (selectedTags.value.length > 0) {
data.append('tag_ids', selectedTags.value.join(','));
}
// Optimize Image and append to FormData
if (imageFile.value) {
const optimizeData = new FormData();
optimizeData.append('file', imageFile.value);
if (imageOptions.value.width) optimizeData.append('width', imageOptions.value.width.toString());
if (imageOptions.value.height) optimizeData.append('height', imageOptions.value.height.toString());
optimizeData.append('quality', imageOptions.value.quality.toString());
optimizeData.append('format', imageOptions.value.format);
const optimizedBlob = await $fetch<Blob>('/api/optimize', {
method: 'POST',
body: optimizeData,
responseType: 'blob'
});
const ext = imageOptions.value.format === 'jpg' ? 'jpeg' : imageOptions.value.format;
const filename = `img-${Date.now()}.${ext === 'jpeg' ? 'jpg' : ext}`;
const optimizedFile = new File([optimizedBlob], filename, { type: `image/${ext}` });
data.append('images', optimizedFile);
}
await $fetch('/api/v1/posts', {
method: 'POST',
baseURL: config.public.BASE_API_URL,
headers: { Authorization: `Bearer ${(authData.value as any)?.accessToken}` },
body: data
});
Swal.fire('Başarılı', 'Gönderi başarıyla oluşturuldu.', 'success');
router.push('/admin/blog/posts');
} catch (error: any) {
console.error(error);
Swal.fire('Hata', error.data?.message || 'Bir hata oluştu.', 'error');
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchData();
});
</script>