import {CONSTANTS} from './constants.js';
import {
    closeMobileMenu,
    ensureProgressOverlay,
    formatFileSize,
    hideLoading,
    hideProgress,
    initTheme,
    isMobile,
    showLoading,
    showMessage,
    showProgress,
    toggleMobileMenu,
    updatePageTitle,
    updateProgress,
    withButtonLoading
} from './ui.js';
import {MarkdownEditor} from './markdown.js';
import {destroyPhotoViewer, initPhotoViewer, openPhotoSwipe} from './photos.js';
import {AudioRecorder} from './audio-recorder.js';

export class MemberCenter {
    constructor() {
        this.state = {
            currentTheme: 'light',
            currentDeleteItem: null
        };
        this.markdownEditor = null;
        this.audioRecorder = null;
        this.initialize();
    }

    initialize() {
        ensureProgressOverlay();
        this.bindEvents();
        this.initPjax();
        this.state.currentTheme = initTheme();
        this.initMarkdown();
        this.initPhotoViewer();
        if (document.getElementById('startRecording')) {
            this.initAudioRecorder();
        }
    }

    initPjax() {
        $.pjax.defaults.timeout = 5000;
        const mainSelectors = [
            'nav.member-nav > ul > li.nav-item > a',
            '#newArticle', '#newMumble', '#newTimeline', '#uploadPhoto',
            '.page-header .btn-secondary',
            '.list-actions a',
            '.photo-actions a'
        ];
        $(document).pjax(mainSelectors.join(', '), '#pjax-container', {scrollTo: 0});
        $(document).pjax('#member-article .pagination a', '#member-article', {scrollTo: 0});
        $(document).pjax('#member-mumble .pagination a', '#member-mumble', {scrollTo: 0});
        $(document).pjax('#member-timeline .pagination a', '#member-timeline', {scrollTo: 0});
        $(document).pjax('#member-photo .pagination a', '#member-photo', {scrollTo: 0});

        $(document).on('pjax:send', () => $(CONSTANTS.SELECTORS.loadingOverlay).addClass('active'));
        $(document).on('pjax:complete', () => $(CONSTANTS.SELECTORS.loadingOverlay).removeClass('active'));
        $(document).on('pjax:end', () => {
            window.scrollTo(0, 0);
            if (document.getElementById('startRecording')) {
                this.initAudioRecorder();
            }
            this.initMarkdown();
            this.initPhotoViewer();
            updatePageTitle();
            if (isMobile()) {
                closeMobileMenu();
            }
        });
    }

    initMarkdown() {
        const articleContentEl = document.getElementById('articleContent');
        const mumbleContentEl = document.getElementById('mumbleContent');
        const timelineContentEl = document.getElementById('timelineContent');
        const editorEl = articleContentEl || mumbleContentEl || timelineContentEl;
        if (!editorEl) {
            this.markdownEditor = null;
            return;
        }

        const markdownValueEl = document.getElementById('txt_' + editorEl.id);
        if (!markdownValueEl) {
            this.markdownEditor = null;
            return;
        }

        const uploadAddress = document.getElementById('uploadAddress')?.value;
        const markdown = markdownValueEl.value || '';

        this.markdownEditor = new MarkdownEditor({
            elementId: editorEl.id,
            markdown,
            theme: this.state.currentTheme,
            editOnly: true,
            uploadAddress,
            fileUpload: (file, cb, uploadUrl) => this.fileUpload(file, cb, uploadUrl),
            markdownValueEl
        });
    }

    initPhotoViewer() {
        destroyPhotoViewer();

        const photoPreview = document.getElementById('photoPreview');
        const loadingPlaceholder = document.getElementById('imageLoadingPlaceholder');

        if (!photoPreview) return;

        // 处理已存在的图片(编辑模式)
        if (photoPreview.src && photoPreview.src !== '') {
            // 检查图片是否已经加载完成
            if (photoPreview.complete && photoPreview.naturalHeight !== 0) {
                // 图片已加载完成
                if (loadingPlaceholder) {
                    loadingPlaceholder.remove();
                }
                photoPreview.style.display = 'block';
            } else {
                // 图片还在加载中,添加加载事件监听
                photoPreview.onload = () => {
                    if (loadingPlaceholder) {
                        loadingPlaceholder.remove();
                    }
                    photoPreview.style.display = 'block';
                };

                photoPreview.onerror = () => {
                    if (loadingPlaceholder) {
                        loadingPlaceholder.innerHTML = '<i class="fa fa-exclamation-circle" style="font-size: 40px; color: var(--danger-color);"></i><span style="color: var(--text-secondary); margin-top: 8px;">照片加载失败</span>';
                    }
                };
            }
        }

        // 初始化照片查看器
        initPhotoViewer();
    }

    initAudioRecorder() {
        if (!this.audioRecorder) {
            this.audioRecorder = new AudioRecorder({
                onMessage: (msg, type) => showMessage(msg, type),
                onLoading: (show, msg) => show ? showLoading(msg) : hideLoading()
            });
        }
        this.audioRecorder.init();
    }

    bindEvents() {
        this.bindNavigation();
        this.bindSearch();
        this.bindPublish();
        this.bindTagInput();
        this.bindPhotoUpload();
        this.bindModal();
        this.bindViewContent();
        this.bindKeyboard();
        $(document).on('click', CONSTANTS.SELECTORS.menuToggle, () => toggleMobileMenu());
        $(document).on('click', CONSTANTS.SELECTORS.sidebarOverlay, () => closeMobileMenu());
        $(window).on('resize', () => {
            if ($(window).width() > CONSTANTS.BREAKPOINTS.TABLET) closeMobileMenu();
        });
    }

    bindNavigation() {
        $(document).on('click', CONSTANTS.SELECTORS.navItem, (e) => {
            $(CONSTANTS.SELECTORS.navItem).removeClass('active');
            $(e.currentTarget).addClass('active');
        });
    }

    bindSearch() {
        $(document).on('click', '#searchArticles', () => {
            const title = $('#articleTitleSearch').val();
            const tag = $('#articleTagSearch').val();
            const url = new URL('/member/articles', window.location.origin);
            if (title) url.searchParams.set('title', title);
            if (tag) url.searchParams.set('tag', tag);
            $.pjax({url: url.href, container: '#member-article', push: true});
        });
        $(document).on('click', '#clearArticles', () => {
            $('#articleTitleSearch').val('');
            $('#articleTagSearch').val('');
            $('#searchArticles').click();
        });
        $(document).on('keyup', '#articleTitleSearch', (e) => {
            if (e.key === 'Enter') $('#searchArticles').click();
        });

        $(document).on('click', '#searchMumbles', () => {
            const content = $('#mumbleContentSearch').val();
            const url = new URL('/member/mumbles', window.location.origin);
            if (content) url.searchParams.set('content', content);
            $.pjax({url: url.href, container: '#member-mumble', push: true});
        });
        $(document).on('click', '#clearMumbles', () => {
            $('#mumbleContentSearch').val('');
            $('#searchMumbles').click();
        });
        $(document).on('keyup', '#mumbleContentSearch', (e) => {
            if (e.key === 'Enter') $('#searchMumbles').click();
        });

        $(document).on('click', '#searchTimeline', () => {
            const content = $('#timelineSearch').val();
            const url = new URL('/member/timeline', window.location.origin);
            if (content) url.searchParams.set('content', content);
            $.pjax({url: url.href, container: '#member-timeline', push: true});
        });
        $(document).on('click', '#clearTimeline', () => {
            $('#timelineSearch').val('');
            $('#searchTimeline').click();
        });
        $(document).on('keyup', '#timelineSearch', (e) => {
            if (e.key === 'Enter') $('#searchTimeline').click();
        });

        $(document).on('click', '#searchPhotos', () => {
            const tag = $('#photoTagSearch').val();
            const desc = $('#photoDescSearch').val();
            const url = new URL('/member/photos', window.location.origin);
            if (tag) url.searchParams.set('tag', tag);
            if (desc) url.searchParams.set('description', desc);
            $.pjax({url: url.href, container: '#member-photo', push: true});
        });
        $(document).on('click', '#clearPhotos', () => {
            $('#photoTagSearch').val('');
            $('#photoDescSearch').val('');
            $('#searchPhotos').click();
        });
        $(document).on('keyup', '#photoDescSearch', (e) => {
            if (e.key === 'Enter') $('#searchPhotos').click();
        });
    }

    bindPublish() {
        $(document).on('click', '#saveProfile', () => this.saveProfile());
        $(document).on('click', '#logout', () => this.logout());
        $(document).on('click', '#uploadAvatar', () => $('#avatarFile').click());
        $(document).on('change', '#avatarFile', (e) => this.handleAvatarUpload(e));

        $(document).on('click', '#confirmPublish', async (e) => {
            const $btn = $(e.currentTarget);
            const action = $btn.data('action');
            if (action && typeof this[action] === 'function') {
                await withButtonLoading($btn, async () => {
                    await this[action]();
                });
            }
        });
    }

    bindTagInput() {
        $(document).on('click', '#addArticleTag', () => {
            this.addTagFromInput('articleExtraTags', 'articleTags');
        });
        $(document).on('keydown', '#articleExtraTags', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                this.addTagFromInput('articleExtraTags', 'articleTags');
            }
        });

        $(document).on('click', '#addPhotoTag', () => {
            this.addTagFromInput('photoExtraTags', 'photoTags');
        });
        $(document).on('keydown', '#photoExtraTags', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                this.addTagFromInput('photoExtraTags', 'photoTags');
            }
        });
    }

    addTagFromInput(inputId, containerId) {
        const input = document.getElementById(inputId);
        if (!input) return;
        const value = (input.value || '').trim();
        if (!value) return;
        this.addTagToSelector(containerId, value);
        input.value = '';
    }

    addTagToSelector(containerId, value) {
        const container = document.getElementById(containerId);
        if (!container) return;

        const existing = Array.from(container.querySelectorAll('input[type="checkbox"]'))
            .find((input) => input.value.trim().toLowerCase() === value.toLowerCase());
        if (existing) {
            existing.checked = true;
            return;
        }

        const label = document.createElement('label');
        label.className = 'tag-option';

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.value = value;
        checkbox.checked = true;

        const span = document.createElement('span');
        const icon = document.createElement('i');
        icon.className = 'fa fa-check';
        const text = document.createElement('em');
        text.textContent = value;

        span.appendChild(icon);
        span.appendChild(text);

        label.appendChild(checkbox);
        label.appendChild(span);
        container.appendChild(label);
    }

    bindPhotoUpload() {
        $(document).on('change', '#photoFile', (event) => {
            const file = event.target.files[0];
            if (!file) return;
            const reader = new FileReader();
            reader.onload = (e) => {
                const $photoPreview = $('#photoPreview');
                $photoPreview.attr('src', e.target.result);
                $photoPreview.show();
                $('#imageLoadingPlaceholder').remove();
                $('#previewContainer').addClass('active');
                $('#dropZone').hide();
                $('#fileName').text(file.name);
                $('#fileSize').text(formatFileSize(file.size));
                initPhotoViewer();
            };
            reader.readAsDataURL(file);
        });

        $(document).on('click', '#removeFileBtn', () => {
            $('#photoFile').val('');
            $('#previewContainer').removeClass('active');
            $('#dropZone').show();
            $('#photoPreview').attr('src', '');
            destroyPhotoViewer();
        });

        $(document).on('click', '#viewPhotoBtn', () => {
            const photoPreview = document.getElementById('photoPreview');
            if (photoPreview && photoPreview.src) {
                openPhotoSwipe(photoPreview);
            }
        });

        const dropZone = document.getElementById('dropZone');
        if (dropZone) {
            ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
                dropZone.addEventListener(eventName, preventDefaults, false);
            });
            ['dragenter', 'dragover'].forEach(eventName => dropZone.addEventListener(eventName, () => dropZone.classList.add('drag-over'), false));
            ['dragleave', 'drop'].forEach(eventName => dropZone.addEventListener(eventName, () => dropZone.classList.remove('drag-over'), false));
            dropZone.addEventListener('drop', (e) => {
                const dt = e.dataTransfer;
                const files = dt.files;
                const fileInput = document.getElementById('photoFile');
                if (files.length > 0) {
                    fileInput.files = files;
                    const event = new Event('change', {bubbles: true});
                    fileInput.dispatchEvent(event);
                }
            }, false);
        }

        function preventDefaults(e) {
            e.preventDefault();
            e.stopPropagation();
        }
    }

    bindModal() {
        $('#closeDeleteModal').on('click', () => this.hideDeleteModal());
        $('#cancelDelete').on('click', () => this.hideDeleteModal());
        $('#confirmDelete').on('click', () => this.confirmDelete());
        $(document).on('click', '.modal-overlay', (e) => {
            if (e.target === e.currentTarget) this.hideDeleteModal();
        });
    }

    bindViewContent() {
        const $viewContentModal = $('#viewContentModal');
        $('#closeViewContentModal, #closeViewContentBtn').on('click', () => $viewContentModal.hide());
        $viewContentModal.on('click', (e) => {
            if (e.target === e.currentTarget) $('#viewContentModal').hide();
        });
        $(document).on('click', '.mobile-content-toggle', (e) => this.toggleMobileContent(e.target));
    }

    bindKeyboard() {
        $(document).on('keydown', (e) => {
            if (e.key === 'Escape') this.hideDeleteModal();
        });
    }

    toggleMobileContent(btn) {
        const $btn = $(btn);
        const $wrapper = $btn.prev('.mobile-content-wrapper');
        if ($wrapper.hasClass('collapsed')) {
            $wrapper.removeClass('collapsed');
            $btn.html('<i class="fa fa-angle-up"></i> 收起内容');
        } else {
            $wrapper.addClass('collapsed');
            $btn.html('<i class="fa fa-angle-down"></i> 展开全文');
        }
    }

    showContent(content, type = 'markdown') {
        let htmlContent = content;
        if (type === 'markdown' && typeof showdown !== 'undefined') {
            const converter = new showdown.Converter();
            htmlContent = converter.makeHtml(content);
        } else if (type === 'text') {
            htmlContent = $('<div>').text(content).html().replace(/\n/g, '<br>');
        }
        const $content = $('<div>').html(htmlContent);
        $content.find('img').css({'max-width': '100%', 'height': 'auto'});
        $('#viewContentModalBody').html($content);
        $('#viewContentModal').css('display', 'flex');
        if (typeof Prism !== 'undefined') {
            try {
                const container = document.getElementById('viewContentModalBody');
                const codeBlocks = container.querySelectorAll('pre code');
                if (codeBlocks.length > 0) Prism.highlightAllUnder(container);
            } catch (e) {
                console.error('代码高亮失败:', e);
            }
        }
    }

    // --- data actions ---
    async saveProfile() {
        try {
            const formData = new FormData();
            formData.append('name', $('#nickname').val().trim());
            formData.append('sex', $('#gender').val());
            formData.append('sign', $('#signature').val().trim());
            showLoading();
            const response = await fetch('/Account/UpdateInfo', {method: 'POST', body: formData});
            const result = await response.json();
            hideLoading();
            if (!result.success) return showMessage(result.message || '保存失败', 'error');
            showMessage('保存成功', 'success');
        } catch (error) {
            console.error('保存个人资料失败:', error);
            hideLoading();
            showMessage('保存失败,请重试', 'error');
        }
    }

    async logout() {
        if (!confirm('确定要退出登录吗?')) return;
        showLoading();
        try {
            const response = await fetch('/Account/LogOut', {method: 'POST'});
            if (response.redirected) {
                window.location.href = response.url;
            } else if (response.ok) {
                window.location.href = '/';
            } else {
                showMessage('退出失败', 'error');
            }
        } catch (error) {
            console.error('退出失败:', error);
            showMessage('退出失败', 'error');
        } finally {
            hideLoading();
        }
    }

    async handleAvatarUpload(event) {
        const file = event.target.files[0];
        if (!file) return;
        if (!file.type.startsWith('image/')) {
            showMessage('请选择图片文件', 'error');
            return;
        }
        const maxSize = 5 * 1024 * 1024;
        if (file.size > maxSize) {
            showMessage('图片文件不能超过5MB', 'error');
            return;
        }
        const reader = new FileReader();
        reader.onload = (e) => {
            $('#avatarPreview, #userAvatar').attr('src', e.target.result);
        };
        reader.readAsDataURL(file);
        await this.uploadImage(file);
    }

    async uploadImage(file) {
        showLoading();
        const form = new FormData();
        form.append('avatar', file);
        try {
            const response = await fetch('/Account/UpdateAvatar', {method: 'POST', body: form});
            const result = await response.json();
            hideLoading();
            if (!result.success) return showMessage(result.msg, 'error');
            showMessage('头像上传成功', 'success');
        } catch (error) {
            hideLoading();
            showMessage('上传发生错误', 'error');
        }
    }

    deleteItem(type, id) {
        this.state.currentDeleteItem = {type, id};
        $(CONSTANTS.SELECTORS.deleteModal).show();
    }

    hideDeleteModal() {
        $(CONSTANTS.SELECTORS.deleteModal).hide();
        this.state.currentDeleteItem = null;
    }

    confirmDelete() {
        if (this.state.currentDeleteItem) {
            showLoading();
            setTimeout(() => {
                hideLoading();
                this.hideDeleteModal();
                showMessage('删除成功', 'success');
                $.pjax.reload('#' + this.getCurrentContainerId());
            }, 300);
        }
    }

    getCurrentContainerId() {
        if (location.pathname.includes('articles')) return 'member-article';
        if (location.pathname.includes('mumbles')) return 'member-mumble';
        if (location.pathname.includes('timeline')) return 'member-timeline';
        if (location.pathname.includes('photos')) return 'member-photo';
        return 'pjax-container';
    }

    async publishArticle() {
        const submitAddressEl = document.getElementById('submitAddress');
        if (!submitAddressEl) return;
        const action = submitAddressEl.value;
        const id = document.getElementById('articleId').value;
        const title = document.getElementById('articleTitle').value;
        const tagsContainer = document.getElementById('articleTags');
        const tags = Array.from(tagsContainer?.querySelectorAll('input[type="checkbox"]:checked') || [])
            .map((x) => x.value);
        const introduction = document.getElementById('articleIntroduction').value;
        let markdown = '';
        let content = '';
        if (this.markdownEditor) {
            markdown = this.markdownEditor.getMarkdown();
            content = this.markdownEditor.getHtml();
        } else {
            const txt = document.getElementById('txt_articleContent');
            markdown = txt ? txt.value : '';
            content = markdown;
        }
        if (!title.trim()) return showMessage('标题不能为空', 'warning');
        if (tags.length === 0) return showMessage('请选择标签或输入补充标签', 'warning');
        if (!introduction.trim()) return showMessage('简介不能为空', 'warning');
        if (!markdown.trim() || markdown.length < 5) return showMessage('内容不能为空', 'warning');

        const formData = new FormData();
        if (id.trim() !== '') formData.append('id', id);
        formData.append('title', title);
        for (const tag of tags) formData.append('tags', tag);
        formData.append('introduction', introduction);
        formData.append('markdown', markdown);
        formData.append('content', content);

        try {
            const response = await fetch(action, {method: 'POST', body: formData});
            const result = await response.json();
            if (!result.success) return showMessage(result.msg || '发布失败', 'error');
            showMessage('文章发布成功', 'success');
            $.pjax({url: '/member/articles', container: '#pjax-container'});
        } catch (error) {
            showMessage('发布失败: ' + error.message, 'error');
        }
    }

    async publishMumble() {
        const submitAddressEl = document.getElementById('submitAddress');
        if (!submitAddressEl) return;
        const action = submitAddressEl.value;
        const id = document.getElementById('mumbleId')?.value || '';
        let content = '';
        let html = '';
        if (this.markdownEditor) {
            content = this.markdownEditor.getMarkdown();
            html = this.markdownEditor.getHtml();
        } else {
            const el = document.getElementById('mumbleContent');
            content = el ? el.value : '';
        }
        if (!content || !content.trim() || content.length < 5) return showMessage('请输入内容', 'warning');
        const formData = new FormData();
        if (id.trim() !== '') formData.append('id', id);
        formData.append('markdown', content);
        formData.append('html', html);
        try {
            const response = await fetch(action, {method: 'POST', body: formData});
            const result = await response.json();
            if (!result.success) return showMessage(result.msg || '发布失败', 'error');
            showMessage('碎碎念发布成功', 'success');
            $.pjax({url: '/member/mumbles', container: '#pjax-container'});
        } catch (error) {
            showMessage('发布失败: ' + error.message, 'error');
        }
    }

    async publishTimeline() {
        const submitAddressEl = document.getElementById('submitAddress');
        if (!submitAddressEl) return;
        const action = submitAddressEl.value;
        const id = document.getElementById('timelineId').value;
        const title = document.getElementById('timelineTitle').value;
        const more = document.getElementById('timelineMore').value;
        const date = document.getElementById('timelineDate').value;
        let content = '';
        if (this.markdownEditor) {
            content = this.markdownEditor.getMarkdown();
        } else {
            const el = document.getElementById('timelineContent');
            content = el ? el.value : '';
        }
        if (!title.trim()) return showMessage('标题不能为空', 'warning');
        if (!date.trim()) return showMessage('时间不能为空', 'warning');
        if (!content.trim() || content.length < 5) return showMessage('内容不能为空', 'warning');

        const formData = new FormData();
        if (id.trim() !== '') formData.append('id', id);
        formData.append('title', title);
        formData.append('more', more);
        formData.append('date', date);
        formData.append('content', content);

        try {
            const response = await fetch(action, {method: 'POST', body: formData});
            const result = await response.json();
            if (!result.success) return showMessage(result.msg || '发布失败', 'error');
            showMessage('时间轴发布成功', 'success');
            $.pjax({url: '/member/timeline', container: '#pjax-container'});
        } catch (error) {
            showMessage('发布失败: ' + error.message, 'error');
        }
    }

    async publishPhoto() {
        const submitAddressEl = document.getElementById('submitAddress');
        if (!submitAddressEl) return;
        const action = submitAddressEl.value;
        const id = document.getElementById('photoId')?.value || '';
        const fileInput = document.getElementById('photoFile');
        const file = fileInput.files[0];
        if (id.trim() === '' && !file) return showMessage('请选择照片', 'warning');

        const tagsContainer = document.getElementById('photoTags');

        const description = document.getElementById('photoDescription').value;
        const formData = new FormData();
        if (id.trim() !== '') formData.append('id', id);
        if (file) formData.append('photo', file);
        formData.append('description', description);

        const tags = Array.from(tagsContainer?.querySelectorAll('input[type="checkbox"]:checked') || [])
            .map((x) => x.value);
        for (const tag of tags) {
            formData.append('tags', tag);
        }
        try {
            showProgress('正在上传照片...');
            const result = await this.uploadWithProgress(action, formData, (percent) => updateProgress(percent, '正在上传照片...'));
            if (!result.success) return showMessage(result.msg || '上传失败', 'error');
            showMessage('照片上传成功', 'success');
            $.pjax({url: '/member/photos', container: '#pjax-container'});
        } catch (error) {
            showMessage('上传失败: ' + error.message, 'error');
        } finally {
            hideProgress();
        }
    }

    uploadWithProgress(url, formData, onProgress) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open('POST', url, true);
            if (xhr.upload && onProgress) {
                xhr.upload.onprogress = (e) => {
                    if (e.lengthComputable) {
                        const percent = Math.round((e.loaded / e.total) * 100);
                        onProgress(percent);
                    }
                };
            }
            xhr.onload = () => {
                if (xhr.status >= 200 && xhr.status < 300) {
                    try {
                        const response = JSON.parse(xhr.responseText);
                        resolve(response);
                    } catch (e) {
                        reject(new Error('Invalid JSON response'));
                    }
                } else {
                    reject(new Error(`Upload failed with status ${xhr.status}`));
                }
            };
            xhr.onerror = () => reject(new Error('Network error'));
            xhr.send(formData);
        });
    }

    async fileUpload(file, callback, uploadUrl) {
        const formData = new FormData();
        formData.append('image', file);
        try {
            showProgress('正在上传图片...');
            const result = await this.uploadWithProgress(uploadUrl, formData, (percent) => updateProgress(percent, '正在上传图片...'));
            hideProgress();
            callback(result.url || '');
        } catch (error) {
            hideProgress();
            showMessage('图片上传失败: ' + error.message, 'error');
            console.error('图片上传失败:', error);
            callback('');
        }
    }
}

$(document).ready(() => {
    try {
        window.memberCenter = new MemberCenter();
    } catch (error) {
        console.error('个人中心初始化失败:', error);
    }
});
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

总体说明

  • 这是一个用于“个人中心 / 后台管理”页面的前端类组件实现(MemberCenter),用来初始化页面、绑定交互事件、处理文章/碎碎念/时间轴/照片的发布与上传、图片预览与查看、个人信息与头像更新、删除确认等常见功能。
  • 代码依赖外部模块和库:CONSTANTS(选择器、断点等常量)、ui.js(显示/隐藏 loading、进度、消息、主题切换等 UI 辅助函数)、MarkdownEditor、photos.js(照片查看器相关)、AudioRecorder、jQuery、pjax、showdown(markdown 转 HTML 可选)、Prism(代码高亮 可选)等。

主要结构与初始化

  • MemberCenter 类在构造函数中初始化内部 state(如当前主题、待删除项)、markdown 编辑器和音频录制器引用,并调用 initialize() 完成页面初始化。
  • initialize():
    • ensureProgressOverlay():确保进度 overlay 存在(UI 相关)。
    • 绑定各种事件(bindEvents)。
    • 初始化 pjax(initPjax)以实现局部页面异步加载与导航。
    • 使用 initTheme() 设置当前主题,并初始化 markdown 编辑器、照片查看器,若页面存在录音按钮则初始化音频录制器。

PJAX(局部异步加载)

  • initPjax() 设置 $.pjax 默认超时时间,并为一些导航/分页链接注册 PJAX 容器,避免整页刷新。
  • 监听 pjax 事件(pjax:send/pjax:complete/pjax:end),用于显示/隐藏页面加载遮罩、滚动到顶部、重新初始化编辑器/查看器、更新页面标题、在移动端自动关闭侧栏等。

Markdown 编辑器

  • initMarkdown() 查找编辑器元素(articleContent、mumbleContent、timelineContent 中的一个),以及对应的隐藏 Markdown 值元素 txt_
  • 若存在则创建 MarkdownEditor 实例,传入:元素 id、当前 markdown 内容、主题、上传地址、以及自定义 fileUpload 回调(使用本类的 fileUpload 方法)等。

照片查看与上传

  • initPhotoViewer():
    • 销毁已存在查看器,处理 photoPreview 的加载占位(imageLoadingPlaceholder),对已存在图片做加载成功/失败处理,最后初始化照片查看器(initPhotoViewer)。
  • bindPhotoUpload():
    • 处理 file input 的 change:读取文件并设置预览、显示文件名、大小,初始化查看器。
    • 支持移除预览(removeFileBtn)、查看预览(viewPhotoBtn -> openPhotoSwipe)。
    • 实现拖放上传区域(dropZone)的拖拽事件,阻止默认行为并将拖入文件赋给 file input 并触发 change。
    • formatFileSize 用于显示文件大小(来自 ui.js)。

音频录制

  • initAudioRecorder():如果未创建则用 AudioRecorder 初始化并绑定消息/loading 回调,然后调用 audioRecorder.init()。

事件绑定(bindEvents 和若干子绑定)

  • bindNavigation(): 点击菜单项时设置 active 状态。
  • bindSearch(): 为文章/碎碎念/时间轴/照片提供搜索、清空和回车触发搜索逻辑,利用 PJAX 请求更新列表容器。
  • bindPublish(): 绑定保存资料、登出、头像上传按钮与文件变化,绑定带 data-action 的确认发布按钮,使用 withButtonLoading 给按钮加 loading 状态并执行对应动作(按钮 data-action 对应 MemberCenter 的方法名)。
  • bindTagInput(): 支持输入补充标签并添加到标签选择区(按回车或点击添加)。
  • bindModal(): 删除确认模态框的打开/关闭逻辑与点击遮罩关闭。
  • bindViewContent(): 查看内容模态框(渲染 markdown/text 到模态并显示),并支持代码高亮(Prism)和图片样式限制。
  • bindKeyboard(): 全局 Esc 键用于关闭删除模态框。
  • 还绑定了移动端菜单切换与窗口 resize 行为(在宽度超过断点时自动关闭移动菜单)。

标签管理

  • addTagFromInput(inputId, containerId):从输入框取值并调用 addTagToSelector。
  • addTagToSelector(containerId, value):检查是否已存在同名标签(不区分大小写),若存在则选中,否则动态创建一个带 checkbox 的标签项并选中。

发布/保存/退出等数据操作

  • saveProfile(): 收集昵称、性别、签名,POST 到 /Account/UpdateInfo,显示 loading,并根据返回提示状态。
  • logout(): 弹窗确认后调用 /Account/LogOut(POST),根据响应做重定向或提示。
  • handleAvatarUpload(event):检查文件是否存在、是否图片、大小不超过 5MB,预览头像,并调用 uploadImage 上传。
  • uploadImage(file):将头像文件 formData POST 到 /Account/UpdateAvatar,显示 loading 并处理返回。
  • deleteItem(type, id) / hideDeleteModal() / confirmDelete():
    • deleteItem 保存要删除的项到 state 并显示删除模态。
    • confirmDelete 目前为演示性实现:showLoading 后用 setTimeout 模拟删除成功、隐藏 loading、关闭模态并通过 $.pjax.reload 刷新当前列表容器(实际应调用后端删除接口)。
  • getCurrentContainerId(): 根据当前 pathname 返回对应的 pj ax 容器 id(articles/mumbles/timeline/photos),用于 reload。

发布各类内容(文章/碎碎念/时间轴/照片)

  • publishArticle():
    • 收集表单字段:submitAddress(目标 action url)、id、title、tags(checkbox 选中值)、introduction、markdown/html(优先从 markdownEditor 获取)。
    • 做数据校验(标题/标签/简介/内容等),组装 FormData,POST 到 action,处理返回并用 PJAX 导回 articles 列表。
  • publishMumble()、publishTimeline() 类似,收集各自字段,校验后 POST。
  • publishPhoto():
    • 收集照片文件、描述、标签,使用 uploadWithProgress 上传(用于显示进度条),处理返回并 PJAX 返回照片列表,最后隐藏进度。

上传与进度

  • uploadWithProgress(url, formData, onProgress):基于 XMLHttpRequest 实现上传进度回调(xhr.upload.onprogress),返回 Promise,完成后解析 JSON 或抛错。
  • fileUpload(file, callback, uploadUrl):用于 Markdown 编辑器或其他需要上传图片的场景,构建 formData 并调用 uploadWithProgress,回调上传后的图片 URL 或空字符串,并显示/隐藏进度与错误提示。

显示内容模态

  • showContent(content, type):可显示 markdown(如果有 showdown 则转换)、纯文本(转义后换行替换为
    )或直接 HTML;在注入内容后对图片设定 max-width,显示模态,若存在 Prism 则尝试高亮代码块。

其它细节与错误处理

  • 大量使用 showLoading/hideLoading/showProgress/hideProgress/showMessage/updateProgress 等 ui 工具函数来管理 loading、进度与消息提示。
  • 对网络请求使用 try/catch,捕获错误并以 showMessage 或控制台输出进行提示。
  • 部分功能(如 confirmDelete)是本地模拟,需要与后端实际删除接口对接才能完成真实删除。
  • 依赖全局常量 CONSTANTS.SELECTORS 与 CONSTANTS.BREAKPOINTS(例如侧栏、菜单切换选择器、tablet 断点等)。

总结

  • 这是一个典型的前端控制器,整合了页面初始化、局部刷新(pjax)、编辑器/查看器/录音等组件的初始化与交互绑定,封装了文件上传(含进度)、表单提交、模态交互和常用 UI 提示。若要在项目中使用,需要确保相关外部依赖(jQuery、pjax、MarkdownEditor、photos.js、AudioRecorder、ui.js、showdown、Prism 等)已引入,并将模拟或占位的逻辑(例如删除)替换为真实后端 API 调用。
评论加载中...