1
0

Правка фронта

1. Улучшения мобильной версии
2. Улучшения комментариев фронта
3. Единый лоадер UI
This commit is contained in:
2026-01-16 05:41:30 +07:00
parent 36e844d4ea
commit cb075e56be
11 changed files with 246 additions and 162 deletions

View File

@@ -3,7 +3,8 @@
class="comment"
:class="{
'comment--own': isOwn,
'comment--reply': level > 0
'comment--reply': level > 0,
'is-mobile': isMobile
}"
:style="{ marginLeft: level * 24 + 'px' }"
>
@@ -14,8 +15,10 @@
<div class="comment-body">
<div class="comment-header">
<span class="comment-author">{{ comment.author_name }}</span>
<span class="comment-date">{{ formattedDate }}</span>
<div class="comment-meta" :class="{ 'is-mobile': isMobile }">
<span class="comment-author">{{ comment.author_name }}</span>
<span class="comment-date">{{ formattedDate }}</span>
</div>
<div class="comment-actions">
<IconButton
@@ -82,6 +85,9 @@
import { computed, onMounted, onUpdated } from 'vue'
import IconButton from '../ui/IconButton.vue'
import { serverSettings } from '../../api'
import { useMobile } from '../../composables/useMobile'
const { isMobile } = useMobile()
const props = defineProps({
comment: {
@@ -225,13 +231,17 @@ onUpdated(refreshIcons)
.comment-header {
display: flex;
align-items: center;
align-items: flex-start;
gap: 8px;
margin-bottom: 6px;
}
.comment-header .comment-date {
margin-right: auto;
.comment-meta {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.comment-author {
@@ -245,6 +255,13 @@ onUpdated(refreshIcons)
color: var(--text-muted);
}
/* Mobile: дата под ником (MOBILE_BREAKPOINT из config.js) */
.comment-meta.is-mobile {
flex-direction: column;
align-items: flex-start;
gap: 2px;
}
.comment-text {
font-size: 14px;
color: var(--text-secondary);
@@ -261,17 +278,6 @@ onUpdated(refreshIcons)
.comment-actions {
display: flex;
gap: 4px;
opacity: 0;
transition: opacity 0.15s;
}
.comment:hover .comment-actions {
opacity: 1;
}
/* Mobile: иконки видны всегда */
:global(body.is-mobile) .comment-actions {
opacity: 1;
}
.comment-btn-delete:hover {
@@ -364,4 +370,9 @@ onUpdated(refreshIcons)
width: 12px;
height: 12px;
}
/* Mobile: скрыть кнопку скачивания на миниатюре */
.comment.is-mobile .comment-file-download {
display: none;
}
</style>

View File

@@ -2,10 +2,7 @@
<div class="comments-tab">
<!-- Список комментариев -->
<div class="comments-list" ref="commentsListRef">
<div v-if="loading" class="comments-loading">
<span class="loader"></span>
Загрузка комментариев...
</div>
<Loader v-if="loading" text="Загрузка комментариев..." :inline="true" />
<div v-else-if="flatCommentsWithLevel.length === 0" class="comments-empty">
<i data-lucide="message-circle"></i>
@@ -207,6 +204,7 @@ import CommentForm from './CommentForm.vue'
import ConfirmDialog from '../ConfirmDialog.vue'
import SlidePanel from '../ui/SlidePanel.vue'
import RichTextEditor from '../ui/RichTextEditor.vue'
import Loader from '../ui/Loader.vue'
import { commentsApi, commentImageApi, getFullUrl } from '../../api'
import { useMobile } from '../../composables/useMobile'
@@ -316,14 +314,26 @@ const loadComments = async (silent = false) => {
if (hasChanges) {
comments.value = newData
emit('comments-loaded', newData.length)
await nextTick()
refreshIcons()
// Для первичной загрузки: убираем loading, ждём рендер, прокручиваем мгновенно
if (!silent) {
loading.value = false
await nextTick()
refreshIcons()
// Мгновенная прокрутка к последнему комментарию
if (commentsListRef.value) {
commentsListRef.value.scrollTop = commentsListRef.value.scrollHeight
}
} else {
await nextTick()
refreshIcons()
}
}
}
} catch (e) {
console.error('Ошибка загрузки комментариев:', e)
} finally {
if (!silent) {
if (!silent && loading.value) {
loading.value = false
}
}
@@ -618,9 +628,11 @@ const previewFile = (file, comment) => {
// Хелперы
const scrollToBottom = () => {
if (commentsListRef.value) {
commentsListRef.value.scrollTop = commentsListRef.value.scrollHeight
}
setTimeout(() => {
if (commentsListRef.value) {
commentsListRef.value.scrollTop = commentsListRef.value.scrollHeight
}
}, 100)
}
const refreshIcons = () => {
@@ -683,10 +695,11 @@ defineExpose({
<style scoped>
.comments-tab {
height: 100%;
display: flex;
flex-direction: column;
min-height: 0; /* Важно для flex overflow */
height: 100%;
min-height: 0;
overflow: hidden;
}
.comments-list {
@@ -695,21 +708,29 @@ defineExpose({
display: flex;
flex-direction: column;
gap: 12px;
padding: 2px; /* Для outline при редактировании */
padding-right: 6px;
margin-bottom: 16px;
min-height: 100px;
max-height: calc(100vh - 400px);
padding: 0 6px 16px 2px;
min-height: 0;
}
/* Mobile: убираем max-height, используем flex */
:global(body.is-mobile) .comments-list {
max-height: none;
min-height: 50px;
margin-bottom: 12px;
/* Стилизация скроллбара */
.comments-list::-webkit-scrollbar {
width: 6px;
}
.comments-list::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.04);
border-radius: 3px;
}
.comments-list::-webkit-scrollbar-thumb {
background: rgba(0, 212, 170, 0.3);
border-radius: 3px;
}
.comments-list::-webkit-scrollbar-thumb:hover {
background: rgba(0, 212, 170, 0.5);
}
.comments-loading,
.comments-empty {
display: flex;
flex-direction: column;
@@ -727,21 +748,6 @@ defineExpose({
opacity: 0.5;
}
.loader {
width: 24px;
height: 24px;
border: 2px solid rgba(255, 255, 255, 0.1);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* ========== Edit Panel Styles ========== */
.edit-panel-header {
display: flex;