.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -51,6 +51,13 @@ const menuItems = [
|
||||
iconColor: 'text-primary-500',
|
||||
route: '/marketing-analysis/analysis'
|
||||
},
|
||||
{
|
||||
id: 'analysis-dashboard',
|
||||
label: 'Dashboard',
|
||||
icon: 'pi pi-chart-line',
|
||||
iconColor: 'text-primary-500',
|
||||
route: '/marketing-analysis/dashboard'
|
||||
},
|
||||
{
|
||||
id: 'analyses-list',
|
||||
label: 'Мои анализы',
|
||||
|
||||
@@ -162,16 +162,46 @@
|
||||
<p class="text-sm font-semibold text-surface-900 dark:text-surface-0">{{ post.theme }}</p>
|
||||
</div>
|
||||
<!-- Изображение поста -->
|
||||
<div v-if="hasImage(post) && !hasImageError(`${date}-${idx}`)" class="mb-3">
|
||||
<div v-if="hasImage(post) && !hasImageError(`${date}-${idx}`)" class="mb-3" style="position: relative">
|
||||
<img :src="getImageUrl(post)" :alt="post.theme" class="post-image" loading="lazy" @error="handleImageError(`${date}-${idx}`)" />
|
||||
<Button
|
||||
icon="pi pi-refresh"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
class="regenerate-image-btn"
|
||||
:loading="isImageRegenerating(getPostIndex(post))"
|
||||
:disabled="isImageRegenerating(getPostIndex(post))"
|
||||
v-tooltip.top="'Регенерировать изображение'"
|
||||
@click="handleRegenerateImage(post, getPostIndex(post))"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="hasImage(post) && hasImageError(`${date}-${idx}`)" class="mb-3 no-image-placeholder">
|
||||
<div v-else-if="hasImage(post) && hasImageError(`${date}-${idx}`)" class="mb-3 no-image-placeholder" style="position: relative">
|
||||
<i class="pi pi-image text-4xl text-surface-400 dark:text-surface-600"></i>
|
||||
<p class="text-xs text-surface-500 dark:text-surface-400 mt-2">Изображение недоступно</p>
|
||||
<Button
|
||||
icon="pi pi-refresh"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
class="regenerate-image-btn"
|
||||
:loading="isImageRegenerating(getPostIndex(post))"
|
||||
:disabled="isImageRegenerating(getPostIndex(post))"
|
||||
v-tooltip.top="'Регенерировать изображение'"
|
||||
@click="handleRegenerateImage(post, getPostIndex(post))"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="mb-3 no-image-placeholder">
|
||||
<div v-else class="mb-3 no-image-placeholder" style="position: relative">
|
||||
<i class="pi pi-image text-4xl text-surface-400 dark:text-surface-600"></i>
|
||||
<p class="text-xs text-surface-500 dark:text-surface-400 mt-2">Изображение не сгенерировано</p>
|
||||
<Button
|
||||
icon="pi pi-refresh"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
class="regenerate-image-btn"
|
||||
:loading="isImageRegenerating(getPostIndex(post))"
|
||||
:disabled="isImageRegenerating(getPostIndex(post))"
|
||||
v-tooltip.top="'Регенерировать изображение'"
|
||||
@click="handleRegenerateImage(post, getPostIndex(post))"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<p class="text-sm font-medium text-surface-600 dark:text-surface-400 mb-1">Текст поста:</p>
|
||||
@@ -222,16 +252,46 @@
|
||||
<p class="text-sm font-semibold text-surface-900 dark:text-surface-0">{{ post.theme }}</p>
|
||||
</div>
|
||||
<!-- Изображение поста -->
|
||||
<div v-if="hasImage(post) && !hasImageError(idx)" class="mb-3">
|
||||
<div v-if="hasImage(post) && !hasImageError(idx)" class="mb-3" style="position: relative">
|
||||
<img :src="getImageUrl(post)" :alt="post.theme" class="post-image" loading="lazy" @error="handleImageError(idx)" />
|
||||
<Button
|
||||
icon="pi pi-refresh"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
class="regenerate-image-btn"
|
||||
:loading="isImageRegenerating(idx)"
|
||||
:disabled="isImageRegenerating(idx)"
|
||||
v-tooltip.top="'Регенерировать изображение'"
|
||||
@click="handleRegenerateImage(post, idx)"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="hasImage(post) && hasImageError(idx)" class="mb-3 no-image-placeholder">
|
||||
<div v-else-if="hasImage(post) && hasImageError(idx)" class="mb-3 no-image-placeholder" style="position: relative">
|
||||
<i class="pi pi-image text-4xl text-surface-400 dark:text-surface-600"></i>
|
||||
<p class="text-xs text-surface-500 dark:text-surface-400 mt-2">Изображение недоступно</p>
|
||||
<Button
|
||||
icon="pi pi-refresh"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
class="regenerate-image-btn"
|
||||
:loading="isImageRegenerating(idx)"
|
||||
:disabled="isImageRegenerating(idx)"
|
||||
v-tooltip.top="'Регенерировать изображение'"
|
||||
@click="handleRegenerateImage(post, idx)"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="mb-3 no-image-placeholder">
|
||||
<div v-else class="mb-3 no-image-placeholder" style="position: relative">
|
||||
<i class="pi pi-image text-4xl text-surface-400 dark:text-surface-600"></i>
|
||||
<p class="text-xs text-surface-500 dark:text-surface-400 mt-2">Изображение не сгенерировано</p>
|
||||
<Button
|
||||
icon="pi pi-refresh"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
class="regenerate-image-btn"
|
||||
:loading="isImageRegenerating(idx)"
|
||||
:disabled="isImageRegenerating(idx)"
|
||||
v-tooltip.top="'Регенерировать изображение'"
|
||||
@click="handleRegenerateImage(post, idx)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<p class="text-sm font-medium text-surface-600 dark:text-surface-400 mb-1">Текст поста:</p>
|
||||
@@ -329,6 +389,7 @@ const missingPlatforms = ref([]);
|
||||
const imageErrors = ref(new Set()); // Track image load errors by post index
|
||||
const imageBlobUrls = ref({}); // Cache blob URLs for images with auth (reactive object)
|
||||
const executingTasks = ref(new Set()); // Track which tasks are currently being executed
|
||||
const regeneratingImages = ref(new Set()); // Track which images are being regenerated (by post index)
|
||||
|
||||
// Computed
|
||||
const postsByDate = computed(() => {
|
||||
@@ -633,6 +694,117 @@ const isTaskExecuting = (taskId) => {
|
||||
return taskId ? executingTasks.value.has(taskId) : false;
|
||||
};
|
||||
|
||||
// Get post index in postCalendar array
|
||||
const getPostIndex = (post) => {
|
||||
if (!strategyData.value?.strategy?.postCalendar) return -1;
|
||||
return strategyData.value.strategy.postCalendar.findIndex((p) => p === post);
|
||||
};
|
||||
|
||||
// Check if image is regenerating
|
||||
const isImageRegenerating = (postIndex) => {
|
||||
return postIndex >= 0 && regeneratingImages.value.has(postIndex);
|
||||
};
|
||||
|
||||
// Handle regenerate image
|
||||
const handleRegenerateImage = async (post, postIndex) => {
|
||||
if (!strategyId.value || postIndex < 0) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Ошибка',
|
||||
detail: 'Не удалось определить параметры для регенерации изображения',
|
||||
life: 3000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to regenerating set
|
||||
regeneratingImages.value.add(postIndex);
|
||||
|
||||
try {
|
||||
const result = await MarketingService.regenerateImage(strategyId.value, postIndex);
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Изображение регенерируется',
|
||||
detail: 'Изображение успешно отправлено на регенерацию. Оно будет обновлено через несколько секунд.',
|
||||
life: 5000
|
||||
});
|
||||
|
||||
// Reload strategy data after a short delay to get updated image
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const updatedStrategy = await MarketingService.getStrategy(strategyId.value);
|
||||
if (updatedStrategy.status === 'completed' && updatedStrategy.strategy) {
|
||||
// Update the post in strategyData
|
||||
if (updatedStrategy.strategy.postCalendar && updatedStrategy.strategy.postCalendar[postIndex]) {
|
||||
const updatedPost = updatedStrategy.strategy.postCalendar[postIndex];
|
||||
|
||||
// Update the post in current strategyData
|
||||
if (strategyData.value?.strategy?.postCalendar) {
|
||||
strategyData.value.strategy.postCalendar[postIndex] = updatedPost;
|
||||
}
|
||||
|
||||
// Clear image error for this post
|
||||
imageErrors.value.delete(postIndex);
|
||||
imageErrors.value.delete(`${post.publishDate}-${postIndex}`);
|
||||
|
||||
// Reload image if it has a filename
|
||||
if (updatedPost.imageFilename) {
|
||||
// Remove old blob URL if exists
|
||||
if (imageBlobUrls.value[updatedPost.imageFilename]) {
|
||||
URL.revokeObjectURL(imageBlobUrls.value[updatedPost.imageFilename]);
|
||||
}
|
||||
|
||||
// Load new image
|
||||
const blobUrl = await MarketingService.loadImageAsBlobUrl(updatedPost.imageFilename);
|
||||
if (blobUrl) {
|
||||
imageBlobUrls.value[updatedPost.imageFilename] = blobUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при обновлении стратегии после регенерации:', error);
|
||||
}
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при регенерации изображения:', error);
|
||||
|
||||
// Handle specific error types
|
||||
let errorMessage = error.message || 'Не удалось регенерировать изображение. Попробуйте позже.';
|
||||
let errorSummary = 'Ошибка';
|
||||
let errorLife = 5000;
|
||||
|
||||
if (error.code === 'RATE_LIMIT_EXCEEDED' || error.status === 429) {
|
||||
errorSummary = 'Превышен лимит запросов';
|
||||
errorMessage = error.message || 'Превышен лимит запросов к сервису генерации изображений. Пожалуйста, подождите несколько минут и попробуйте снова.';
|
||||
errorLife = 7000;
|
||||
} else if (error.code === 'UNAUTHORIZED' || error.status === 401) {
|
||||
errorSummary = 'Ошибка авторизации';
|
||||
errorMessage = 'Необходимо войти в систему';
|
||||
} else if (error.code === 'FORBIDDEN' || error.status === 403) {
|
||||
errorSummary = 'Доступ запрещен';
|
||||
errorMessage = 'У вас нет доступа к этой операции';
|
||||
} else if (error.code === 'NOT_FOUND' || error.status === 404) {
|
||||
errorSummary = 'Не найдено';
|
||||
errorMessage = 'Стратегия или пост не найдены';
|
||||
} else if (error.code === 'VALIDATION_ERROR' || error.status === 400) {
|
||||
errorSummary = 'Ошибка валидации';
|
||||
errorMessage = error.message || 'Проверьте правильность данных';
|
||||
}
|
||||
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: errorSummary,
|
||||
detail: errorMessage,
|
||||
life: errorLife
|
||||
});
|
||||
} finally {
|
||||
// Remove from regenerating set
|
||||
regeneratingImages.value.delete(postIndex);
|
||||
}
|
||||
};
|
||||
|
||||
// Export to CSV
|
||||
const exportToCsv = () => {
|
||||
if (!strategyData.value?.strategy?.postCalendar) return;
|
||||
@@ -920,4 +1092,12 @@ onBeforeUnmount(() => {
|
||||
background: var(--surface-800);
|
||||
border-color: var(--surface-700);
|
||||
}
|
||||
|
||||
.regenerate-image-btn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user