Список пользователей

1
Админ
Постов: 162
2
Элита
Постов: 45
3
VIP
Постов: 35
4
Проверенные
Постов: 31
5
Проверенные
Постов: 30
6
Пользователи
Постов: 27
7
VIP
Постов: 26
8
Пользователи
Постов: 24

  • Страница 1 из 1
  • 1
Модуль ВК «Подарки» для Ucoz v 7.0 последняя черная
Дата: Воскресенье, 11.01.2026, 15:29 | Сообщение # 1 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:162
         Регистрация:20.10.2016

Скрипт обновляет данные о подарках через проверку их количества. То есть он не идёт по каждому подарку и не сравнивает детали, а делает это скрыто, быстро и экономно:

Скрипт сначала делает fetch на URL /index/54-USERID- и подсчитывает общее количество подарков с сервера.

Затем он берёт сохранённое значение в localStorage (COUNT_KEY) — это количество подарков, которое было при последней загрузке.

Если текущее количество отличается от сохранённого, скрипт понимает, что появились новые подарки и загружает их полностью (loadAndRenderLastGifts()).

Если количество совпадает, скрипт ничего не подгружает и просто показывает последние 3 подарка из кэша.

То есть логика экономичная и «по числу», чтобы не дергать сервер постоянно. Проверка идёт только через число подарков, а детали обновляются только если это число изменилось.



Источник данных

Код
1. Основной источник — URL /index/54-USERID-.

2. Сервер возвращает HTML/XML с элементами <td> для каждого подарка.

2. Каждый <td> содержит:

4. <img> — картинку подарка

5. <b> — количество одинаковых подарков

6. атрибут onclick — ссылку/действие для открытия профиля отправителя


Кэширование

Код
Скрипт сохраняет подарки в localStorage под ключом giftsData_USERID.

При следующей загрузке он использует кэш, чтобы быстро показать подарки без нового запроса.

Максимальное количество сохранённых подарков — 120.


Мини-превью на странице

Код
На странице показываются последние 3 подарка из массива allGifts.

Для каждого подарка создаётся блок <div class="gift-card"> с изображением (<img>).

Подставляется title с ником пользователя (или «Подарок» по умолчанию).


Попап с полной информацией
Код

При клике на счётчик создаётся попап (.gifts-popup) с листом подарков (.gifts-list).

Для каждого подарка создаётся блок gift-item с:

Аватар отправителя (gift.avatar)

Ник (gift.nick)

Дата подарка (gift.date)

Комментарий (gift.comment) — оформлен в gift-comment-wrapper

Основное изображение (gift.img)

Кнопка вручения (gift.recipientId), которая активна только для других пользователей


Порядок рендера

Код
Сначала берётся кэш из localStorage и рендерятся все подарки в попапе (новые сверху).

Затем асинхронно для каждого подарка вызывается fetchGiftDetails(), которая подгружает актуальные данные: ник, аватар, комментарий, дату.

Если данные обновились, скрипт обновляет DOM конкретного подарка в попапе.

После полной загрузки обновлённые данные сохраняются в кэш.


Итоговая логика

Код
Мини-превью: последние 3 подарка, берутся из кэша.

Полный список: показывается в попапе с аватаром, ником, комментариями, кнопкой «вручить».

Обновление данных: происходит только если число подарков на сервере изменилось.


Исходной код..

Код
<!-- ======= Блок подарков ======= -->
<div class="gifts-block">
    <div class="gifts-header">Подарки</div>
    <div class="gifts-footer" id="gifts-count">Загрузка…</div>
    <div class="gifts-container" id="gifts-container"></div>
</div>

<link rel="stylesheet" href="/css/vk-gifts.css" media="all">

<script>
(async function(){
    const container = document.getElementById('gifts-container');
    const counter   = document.getElementById('gifts-count');
    const userId    = <?$JSENCODE$($USER_ID$)?>;

    const STORAGE_KEY = `giftsData_${userId}`;
    const COUNT_KEY   = `giftsCount_${userId}`;

    let allGifts = [];

    // ==============================================
    //                  Стили
    // ==============================================
    const style = document.createElement('style');
    style.textContent = `
.gifts-popup-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.65);
    backdrop-filter: blur(5px);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10000;
}
.gifts-popup {
    background: #19191c;
    color: #e0e0e0;
    width: 100%;
    max-width: 620px;
    max-height: 90vh;
    border-radius: 16px;
    overflow: hidden;
    box-shadow: 0 22px 70px rgba(0,0,0,0.7);
}
.gifts-popup-header {
    background: #252529;
    padding: 14px 20px;
    font-size: 17px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid #2e2e32;
}
.close-btn {
    font-size: 26px;
    color: #909094;
    cursor: pointer;
    padding: 4px 12px;
    border-radius: 50%;
    transition: all .2s;
}
.close-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }

.gifts-list {
    padding: 8px 0 24px;
    overflow-y: auto;
    overflow-x: hidden;
    height: calc(90vh - 60px);
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    scrollbar-color: #555 #1e1e1e;
}
.gifts-list::-webkit-scrollbar { width: 8px; }
.gifts-list::-webkit-scrollbar-track { background: #1e1e1e; }
.gifts-list::-webkit-scrollbar-thumb { background: #555; border-radius: 4px; }

.gift-item {
    display: flex;
    flex-direction: column;
    padding: 16px 20px;
    border-bottom: 1px solid #252529;
    transition: background 0.2s;
    gap: 10px;
}
.gift-item:hover { background: rgba(255,255,255,0.04); }

.avatar-row {
    display: flex;
    align-items: center;
    gap: 12px;
}
.gift-avatar {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}

.gift-info {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.gift-name {
    font-weight: 600;
    color: #71aaeb;
    font-size: 15px;
}
.gift-date {
    font-size: 13px;
    color: #888;
}

.gift-comment-wrapper {
    --quote-indent: 65px; /* можно менять через JS или inline style */
    margin-left: var(--quote-indent);
}

.gift-comment {
    position: relative;
    background: #222227;
    padding: 11px 14px;
    border-radius: 9px;
    border-left: 3px solid #4d9cff;
    box-shadow: 0 2px 6px rgba(0,0,0,0.2);
}

.gift-comment::before {
    content: "";
    position: absolute;
    --tail-x: 20px;
    --tail-y: -9px;
    --tail-size: 9px;
    top: var(--tail-y);
    left: var(--tail-x);
    border: var(--tail-size) solid transparent;
    border-bottom-color: #222227;
    filter: drop-shadow(0 1px 1px rgba(0,0,0,0.3));
}

.gift-main-img {
    display: flex;
    justify-content: center;
    align-items: center;
    margin: 12px 0;
}
.gift-main-img img {
    max-width: 280px;
    max-height: 280px;
    width: auto;
    height: auto;
    border-radius: 12px;
    box-shadow: 0 6px 24px rgba(0,0,0,0.5);
    object-fit: contain;
}

.gift-action {
    display: flex;
    justify-content: center;
    margin-top: 12px;
}
.gift-action-btn {
    min-width: 240px;
    padding: 10px 24px;
    background: #0a9fff;
    color: white;
    border: none;
    border-radius: 50px;
    font-size: 16px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.25s;
    display: flex;
    align-items: center;
    gap: 8px;
}
.gift-action-btn:hover:not(:disabled) {
    background: #009afc;
    transform: translateY(0px);
    box-shadow: 0 5px 16px rgba(90,136,192,0.35);
}
.gift-action-btn:disabled {
    background: #333;
    color: #777;
    cursor: not-allowed;
}
    `;
    document.head.appendChild(style);

    // ==============================================
    //           Вспомогательные функции
    // ==============================================
    async function getCurrentGiftsCount() {
        try {
            const res = await fetch(`/index/54-${userId}-`);
            if (!res.ok) return null;
            const text = await res.text();
            const xml = new DOMParser().parseFromString(text, 'application/xml');
            const cmd = xml.querySelector('cmd[p="content"]');
            if (!cmd) return 0;

            const temp = document.createElement('div');
            temp.innerHTML = cmd.textContent || '';

            let total = 0;
            temp.querySelectorAll('td[onclick*="_uWnd.reload"]').forEach(td => {
                const b = td.querySelector('b');
                total += b ? (+b.textContent || 1) : 1;
            });
            return total;
        } catch {
            return null;
        }
    }

    // Функцию fetchGiftDetails нужно вставить сюда из предыдущей версии
    // (она отвечает за загрузку ника, аватара, комментария, даты и т.д.)
    // Если её нет — добавь свою рабочую версию

    async function loadAndRenderLastGifts() {
        try {
            const res = await fetch(`/index/54-${userId}-`);
            if (!res.ok) throw new Error("Fetch error");

            const text = await res.text();
            const xml = new DOMParser().parseFromString(text, 'application/xml');
            const cmd = xml.querySelector('cmd[p="content"]');
            if (!cmd) {
                counter.textContent = '0 подарков';
                return;
            }

            const temp = document.createElement('div');
            temp.innerHTML = cmd.textContent || '';

            const tds = [...temp.querySelectorAll('td[onclick*="_uWnd.reload"]')];
            if (!tds.length) {
                counter.textContent = '0 подарков';
                return;
            }

            let freshGifts = [];
            for (const td of tds) {
                const img = td.querySelector('img');
                if (!img) continue;
                const count = td.querySelector('b') ? (+td.querySelector('b').textContent || 1) : 1;
                const onclick = td.getAttribute('onclick');

                for (let i = 0; i < count; i++) {
                    freshGifts.push({
                        img: img.src,
                        onclick,
                        nick: 'Загрузка...',
                        avatar: '/.s/src/profile/img/profile_photo_thumbnail.png',
                        date: new Date().toISOString(),
                        comment: '',
                        profile: '#',
                        recipientId: null
                    });
                }
            }

            let cached = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
            const seen = new Set(cached.map(g => `${g.img}|${g.profile||''}`));

            freshGifts.forEach(g => {
                const key = `${g.img}|${g.profile||''}`;
                if (!seen.has(key)) {
                    cached.push(g);
                    seen.add(key);
                }
            });

            cached = cached.slice(-120);
            localStorage.setItem(STORAGE_KEY, JSON.stringify(cached));
            allGifts = cached;

            // Показ последних 3 на странице
            container.innerHTML = '';
            allGifts.slice(-3).reverse().forEach(g => {
                const el = document.createElement('div');
                el.className = 'gift-card';
                el.innerHTML = `<img src="${g.img}" alt="подарок" loading="lazy">`;
                el.title = g.nick !== 'Загрузка...' ? g.nick : 'Подарок';
                container.appendChild(el);
            });

            const count = allGifts.length;
            counter.textContent = count
                ? `${count} ${count === 1 ? 'подарок' : count < 5 ? 'подарка' : 'подарков'}`
                : 'Нет подарков';

        } catch (e) {
            console.error(e);
            counter.textContent = 'Ошибка загрузки';
        }
    }

    // ==============================================
    //                  Попап
    // ==============================================
    function createPopup() {
        const overlay = document.createElement('div');
        overlay.className = 'gifts-popup-overlay';

        const popup = document.createElement('div');
        popup.className = 'gifts-popup';

        popup.innerHTML = `
<div class="gifts-popup-header">
    Мои подарки
    <span class="close-btn">×</span>
</div>
<div class="gifts-list"></div>
        `;

        overlay.appendChild(popup);
        document.body.appendChild(overlay);

        const close = () => overlay.remove();
        popup.querySelector('.close-btn').onclick = close;
        overlay.onclick = e => { if (e.target === overlay) close(); };

        return popup.querySelector('.gifts-list');
    }

    async function showPopup() {
        if (!allGifts.length) {
            alert('Нет сохранённых подарков');
            return;
        }

        const list = createPopup();

        // Быстрый рендер из кэша (новые сверху)
        allGifts.slice().reverse().forEach(gift => renderGiftItem(list, gift));

        // Фоновая загрузка полной информации
        const updated = [];
        for (const gift of allGifts) {
            const upd = await fetchGiftDetails({ ...gift });
            updated.push(upd);

            const key = `${gift.img}|${gift.profile||''}`;
            const elem = list.querySelector(`[data-key="${key}"]`);
            if (elem) renderGiftItem(list, upd, null, elem);
        }

        localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
        allGifts = updated;
    }

    function renderGiftItem(container, gift, _ = null, existing = null) {
        const key = `${gift.img}|${gift.profile||''}`;
        const div = existing || document.createElement('div');

        if (!existing) {
            div.className = 'gift-item';
            div.dataset.key = key;
            container.appendChild(div);
        }

        div.innerHTML = `
<div class="avatar-row">
    <img class="gift-avatar" src="${gift.avatar}" alt="${gift.nick}" loading="lazy">
    <div class="gift-info">
        <div class="gift-name">${gift.nick}</div>
        <div class="gift-date">${gift.date}</div>
    </div>
</div>

${gift.comment ? `<div class="gift-comment-wrapper"><div class="gift-comment">${gift.comment}</div></div>` : ''}

<div class="gift-main-img">
    <img src="${gift.img}" alt="подарок" loading="lazy">
</div>

<div class="gift-action">
    ${gift.recipientId && String(gift.recipientId) !== String(userId)
        ? `<button class="gift-action-btn" data-recipient="${gift.recipientId}">🎁 Вручить подарок</button>`
        : `<button class="gift-action-btn" disabled>Нельзя вручить себе</button>`
    }
</div>
        `;

        const btn = div.querySelector('.gift-action-btn:not([disabled])');
        if (btn) {
            btn.onclick = () => {
                new _uWnd('AwD', 'Вручить награду', 380, 200,
                    { maxh: 320, minh: 120, closeonesc: 1 },
                    { url: `/index/55-${btn.dataset.recipient}` }
                );
            };
        }
    }

    // ==============================================
    //                   Запуск
    // ==============================================
    async function init() {
        counter.textContent = 'Загрузка...';

        const currentCount = await getCurrentGiftsCount();

        if (currentCount === null) {
            counter.textContent = 'Ошибка';
            return;
        }

        const savedCount = +localStorage.getItem(COUNT_KEY) || 0;

        if (currentCount !== savedCount) {
            localStorage.setItem(COUNT_KEY, currentCount);
            await loadAndRenderLastGifts();
        } else {
            allGifts = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');

            if (allGifts.length) {
                container.innerHTML = '';
                allGifts.slice(-3).reverse().forEach(g => {
                    const el = document.createElement('div');
                    el.className = 'gift-card';
                    el.innerHTML = `<img src="${g.img}" alt="" loading="lazy">`;
                    el.title = g.nick || 'Подарок';
                    container.appendChild(el);
                });

                const c = allGifts.length;
                counter.textContent = `${c} ${c === 1 ? 'подарок' : c < 5 ? 'подарка' : 'подарков'}`;
            } else {
                counter.textContent = 'Нет подарков';
            }
        }

        counter.style.cursor = 'pointer';
        counter.onclick = showPopup;
    }

    init();
})();
</script>


Это наиболее продвинутая на данный момент версия скрипта с точки зрения логики, однако кто знает, какие идеи придут мне в голову завтра. На сегодня это актуальная версия. 27

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
  • Страница 1 из 1
  • 1
Поиск: