'use strict';

/**
 * 个人中心 JavaScript
 * 支持pjax初始化和响应式设计
 * @version 2.1
 * @author 系统管理员
 */

// 常量配置
const CONSTANTS = {
    // 页面类型
    PAGES: {
        PROFILE: 'profile',
        ARTICLES: 'articles',
        MUMBLES: 'mumbles',
        TIMELINE: 'timeline',
        PHOTOS: 'photos'
    },

    // 分页配置
    PAGINATION: {
        DEFAULT_PAGE_SIZE: 10,
        DEFAULT_PAGE_NUM: 1,
        MAX_VISIBLE_PAGES: 5
    },

    // 响应式断点
    BREAKPOINTS: {
        MOBILE: 768, // 统一使用 768px 作为移动端断点
        TABLET: 768,
        DESKTOP: 1024
    },

    // 延迟时间
    DELAYS: {
        LOADING: 300,
        TOAST_AUTO_HIDE: 3000,
        TOAST_HIDE_ANIMATION: 300
    },

    // 消息类型配置
    MESSAGE_TYPES: {
        SUCCESS: {
            type: 'success',
            icon: 'fa-check-circle',
            color: '#059669',
            bgColor: '#ffffff',
            borderColor: '#10b981',
            textColor: '#065f46'
        },
        ERROR: {
            type: 'error',
            icon: 'fa-times-circle',
            color: '#dc2626',
            bgColor: '#ffffff',
            borderColor: '#ef4444',
            textColor: '#7f1d1d'
        },
        WARNING: {
            type: 'warning',
            icon: 'fa-exclamation-triangle',
            color: '#d97706',
            bgColor: '#ffffff',
            borderColor: '#f59e0b',
            textColor: '#92400e'
        },
        INFO: {
            type: 'info',
            icon: 'fa-info-circle',
            color: '#2563eb',
            bgColor: '#ffffff',
            borderColor: '#3b82f6',
            textColor: '#1e40af'
        }
    },

    // 选择器缓存
    SELECTORS: {
        // 导航相关
        navItem: '.nav-item',
        navItemLink: '.nav-item a',
        contentPage: '.content-page',
        currentPageTitle: '#currentPageTitle',

        // 布局相关
        menuToggle: '.menu-toggle',
        sidebarOverlay: '.sidebar-overlay',
        memberSidebar: '.member-sidebar',
        tableContainer: '.table-container',
        photosGrid: '.photos-grid',

        // 模态框相关
        publishModal: '#publishModal',
        deleteModal: '#deleteModal',
        twoFactorModal: '#twoFactorModal',
        modalBody: '#modalBody',
        modalTitle: '#modalTitle',
        confirmPublish: '#confirmPublish',

        // 加载相关
        loadingOverlay: '#loadingOverlay',
        toastContainer: '#toast-container',
        progressOverlay: '#progressOverlay'
    }
};

/**
 * 个人中心主类
 */
class MemberCenter {
    constructor() {
        this.state = {
            currentPage: CONSTANTS.PAGES.PROFILE,
            currentTheme: 'light',
            currentDeleteItem: null
        };

        this.cherryInstance = null;
        this.photoViewer = null;
        this.initialize();
    }

    initialize() {
        try {
            this.bindEvents();
            this.initPjax();
            this.initTheme();
            this.initMarkdownEditor();
            this.initPhotoViewer();
            if ($('#startRecording').length > 0) {
                this.initAudioRecorder();
            }
        } catch (error) {
            console.error('初始化失败:', error);
            this.showMessage('系统初始化失败,请刷新页面重试', 'error');
        }
    }

    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'
        ];

        // 绑定主内容区 PJAX
        $(document).pjax(mainSelectors.join(', '), '#pjax-container', {scrollTo: 0});

        // 绑定列表分页 PJAX
        $(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 ($('#startRecording').length > 0) {
                this.initAudioRecorder();
            }
            this.initMarkdownEditor();
            this.initPhotoViewer();

            // 更新页面标题
            this.updatePageTitle();

            // 移动端:PJAX加载完成后自动关闭菜单
            if (this.isMobile()) {
                this.closeMobileMenu();
            }
        });

        // 初始化进度条 DOM
        if ($(CONSTANTS.SELECTORS.progressOverlay).length === 0) {
            $('body').append(`
                <div class="loading-overlay" id="progressOverlay" style="display: none; flex-direction: column; z-index: 9999;">
                    <div class="loading-spinner" style="width: 80%; max-width: 400px; text-align: center;">
                        <div style="font-size: 1.1rem; margin-bottom: 15px; color: var(--text-color);" id="progressText">准备上传...</div>
                        <div style="width: 100%; height: 10px; background: rgba(0,0,0,0.1); border-radius: 5px; overflow: hidden; position: relative;">
                             <div id="progressBar" class="progress-bar-animated" style="width: 0%; height: 100%; background-color: var(--primary-color); border-radius: 5px; transition: width 0.2s ease;"></div>
                        </div>
                        <div id="progressPercent" style="margin-top: 8px; font-size: 0.9rem; color: var(--text-secondary);">0%</div>
                    </div>
                </div>
            `);
        }

        // 初始化进度条 DOM
        if ($(CONSTANTS.SELECTORS.progressOverlay).length === 0) {
            $('body').append(`
                <div class="loading-overlay" id="progressOverlay" style="display: none; flex-direction: column; z-index: 9999;">
                    <div class="loading-spinner" style="width: 80%; max-width: 400px; text-align: center;">
                        <div style="font-size: 1.1rem; margin-bottom: 15px; color: var(--text-color);" id="progressText">准备上传...</div>
                        <div style="width: 100%; height: 10px; background: rgba(0,0,0,0.1); border-radius: 5px; overflow: hidden; position: relative;">
                             <div id="progressBar" class="progress-bar-animated" style="width: 0%; height: 100%; background-color: var(--primary-color); border-radius: 5px; transition: width 0.2s ease;"></div>
                        </div>
                        <div id="progressPercent" style="margin-top: 8px; font-size: 0.9rem; color: var(--text-secondary);">0%</div>
                    </div>
                </div>
            `);
        }
    }

    /**
     * 更新页面标题
     */
    updatePageTitle() {
        try {
            // 直接获取当前激活的导航菜单文本
            const $activeNav = $('nav.member-nav .nav-item.active a');
            let title = '';

            if ($activeNav.length > 0) {
                // 获取纯文本
                title = $activeNav.text().trim();
            }

            // 如果没有获取到(比如在非导航菜单页面),回退到默认标题
            if (!title) {
                title = '个人中心';
            }

            // 设置文档标题
            document.title = `${title} - 个人中心`;
        } catch (error) {
            console.error('更新页面标题失败:', error);
        }
    }

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

    bindEvents() {
        this.bindMobileMenuEvents();
        this.bindWindowEvents();
        this.bindContentEvents();
        this.bindModalEvents();
        this.bindViewContentEvents();
        this.bindKeyboardEvents();
        this.bindSidebarActive();
    }

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

    initMarkdownEditor() {
        const articleContentEl = document.getElementById('articleContent');
        const mumbleContentEl = document.getElementById('mumbleContent');
        const timelineContentEl = document.getElementById('timelineContent');

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


        const editorEl = (articleContentEl || mumbleContentEl || timelineContentEl);
        if (!editorEl) return;

        const markdownValueEl = document.getElementById('txt_' + editorEl.id);
        if (!markdownValueEl) return;

        const editorId = editorEl.id;
        const markdown = markdownValueEl.value;

        if (this.cherryInstance) {
            this.cherryInstance = null;
        }

        let that = this;
        try {
            this.cherryInstance = new Cherry({
                id: editorId,
                value: markdown,
                height: "100%",
                defaultModel: "editOnly",
                themeSettings: {
                    mainTheme: this.state.currentTheme,
                    codeBlockTheme: 'one-dark',
                },
                engine: {
                    syntax: {
                        codeBlock: {
                            editCode: false,
                            changeLang: false,
                        },
                    }
                },
                toolbars: {
                    toolbar: ['bold', 'italic', 'size', '|', 'color', 'header', 'togglePreview', '|', 'theme',
                        {insert: ['image', 'link', 'hr', 'br', 'code', 'table']}
                    ],
                },
                fileUpload: async function (file, callback) {
                    if (uploadAddress && uploadAddress.trim() !== '') {
                        await that.fileUpload(file, callback, uploadAddress);
                    } else {
                        callback('');
                    }
                }
            });
        } catch (e) {
            console.error('Markdown 编辑器初始化失败:', e);
        }

        markdownValueEl.style.display = 'none';

        if (this.cherryInstance) {
            this.cherryInstance.switchModel('editOnly');
        }
    }

    // 带进度的上传方法
    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 {
            this.showProgress('正在上传图片...');
            const result = await this.uploadWithProgress(uploadUrl, formData, (percent) => {
                this.updateProgress(percent, '正在上传图片...');
            });
            this.hideProgress();
            callback(result["url"]);
        } catch (error) {
            this.hideProgress();
            this.showMessage('图片上传失败: ' + error.message, 'error');
            console.error('图片上传失败:', error);
        }
    }

    bindMobileMenuEvents() {
        $(document).on('click', CONSTANTS.SELECTORS.menuToggle, () => this.toggleMobileMenu());
        $(document).on('click', CONSTANTS.SELECTORS.sidebarOverlay, () => this.closeMobileMenu());
    }

    bindWindowEvents() {
        $(window).on('resize', () => this.handleResize());
    }

    bindContentEvents() {
        $(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));

        // Articles
        $(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();
        });

        // Mumbles
        $(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();
        });

        // Timeline
        $(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();
        });

        // Photos
        $(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();
        });

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

        $(document).on('change', '#photoFile', function (event) {
            const file = event.target.files[0];
            if (!file) return;

            // 显示预览
            const reader = new FileReader();
            reader.onload = (e) => {
                $('#photoPreview').attr('src', e.target.result);
                $('#photoPreview').show();
                $('#imageLoadingPlaceholder').remove();
                $('#previewContainer').addClass('active');
                $('#dropZone').hide();

                // 显示文件信息
                $('#fileName').text(file.name);
                $('#fileSize').text(formatFileSize(file.size));

                // 初始化图片查看器
                memberCenter.initPhotoViewer();
            };
            reader.readAsDataURL(file);
        });

        // 移除文件按钮
        $(document).on('click', '#removeFileBtn', function () {
            $('#photoFile').val('');
            $('#previewContainer').removeClass('active');
            $('#dropZone').show();
            $('#photoPreview').attr('src', '');

            // 清理图片查看器
            if (memberCenter && memberCenter.photoViewer) {
                // 如果查看器正在显示,先关闭它
                try {
                    if (typeof memberCenter.photoViewer.close === 'function') {
                        memberCenter.photoViewer.close();
                    }
                } catch (e) {
                    console.warn('关闭 PhotoSwipe 失败:', e);
                }
                memberCenter.photoViewer = null;
            }
        });

        // 查看大图按钮
        $(document).on('click', '#viewPhotoBtn', function () {
            const photoPreview = document.getElementById('photoPreview');
            if (memberCenter && photoPreview && photoPreview.src) {
                memberCenter.openPhotoSwipe(photoPreview);
            }
        });

        // 拖拽效果
        const dropZone = document.getElementById('dropZone');
        if (dropZone) {
            ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
                dropZone.addEventListener(eventName, preventDefaults, false);
            });

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

            ['dragenter', 'dragover'].forEach(eventName => {
                dropZone.addEventListener(eventName, highlight, false);
            });

            ['dragleave', 'drop'].forEach(eventName => {
                dropZone.addEventListener(eventName, unhighlight, false);
            });

            function highlight(e) {
                dropZone.classList.add('drag-over');
            }

            function unhighlight(e) {
                dropZone.classList.remove('drag-over');
            }

            dropZone.addEventListener('drop', handleDrop, false);

            function handleDrop(e) {
                const dt = e.dataTransfer;
                const files = dt.files;
                const fileInput = document.getElementById('photoFile');

                if (files.length > 0) {
                    fileInput.files = files;
                    // 手动触发 change 事件
                    const event = new Event('change', {bubbles: true});
                    fileInput.dispatchEvent(event);
                }
            }
        }

        function formatFileSize(bytes) {
            if (bytes === 0) return '0 B';
            const k = 1024;
            const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
    }

    bindModalEvents() {
        $('#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();
            }
        });
    }

    bindViewContentEvents() {
        $('#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);
        });
    }

    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 codeBlocks = document.getElementById('viewContentModalBody').querySelectorAll('pre code');
                if (codeBlocks.length > 0) {
                    Prism.highlightAllUnder(document.getElementById('viewContentModalBody'));
                }
            } catch (e) {
                console.error('代码高亮失败:', e);
            }
        }
    }

    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> 展开全文');
        }
    }

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

    // 保存个人资料
    async saveProfile() {
        try {
            const formData = new FormData();
            formData.append('name', $('#nickname').val().trim());
            formData.append('sex', $('#gender').val());
            formData.append('sign', $('#signature').val().trim());

            this.showLoading();

            const response = await fetch('/Account/UpdateInfo', {
                method: 'POST',
                body: formData
            });
            const result = await response.json();
            this.hideLoading();
            if (!result.success) {
                this.showMessage(result.message || '保存失败', 'error');
                return;
            }
            this.showMessage('保存成功', 'success');

        } catch (error) {
            console.error('保存个人资料失败:', error);
            this.hideLoading();
            this.showMessage('保存失败,请重试', 'error');
        }
    }

    // 退出登录
    async logout() {
        if (confirm('确定要退出登录吗?')) {
            this.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 {
                    this.showMessage('退出失败', 'error');
                }
            } catch (error) {
                console.error('退出失败:', error);
                this.showMessage('退出失败', 'error');
            } finally {
                this.hideLoading();
            }
        }
    }

    handleAvatarUpload(event) {
        try {
            const file = event.target.files[0];
            if (!file) return;

            if (!this.validateImageFile(file)) {
                return;
            }

            this.previewImage(file);
            this.uploadImage(file);
        } catch (error) {
            console.error('头像上传失败:', error);
            this.showMessage('头像上传失败', 'error');
        }
    }

    validateImageFile(file) {
        if (!file.type.startsWith('image/')) {
            this.showMessage('请选择图片文件', 'error');
            return false;
        }

        const maxSize = 5 * 1024 * 1024; // 5MB
        if (file.size > maxSize) {
            this.showMessage('图片文件不能超过5MB', 'error');
            return false;
        }

        return true;
    }

    previewImage(file) {
        const reader = new FileReader();
        reader.onload = (e) => {
            $('#avatarPreview, #userAvatar').attr('src', e.target.result);
        };
        reader.readAsDataURL(file);
    }

    // 上传头像实现
    async uploadImage(file) {
        this.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();
            this.hideLoading();
            if (!result.success) {
                this.showMessage(result.msg, 'error');
                return;
            }
            this.showMessage('头像上传成功', 'success');
        } catch (error) {
            this.hideLoading();
            this.showMessage('上传发生错误', 'error');
        }
    }

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

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

    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() {
        // ... (existing publishArticle logic kept as it uses its own endpoints)
        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 tagsSelect = document.getElementById('articleTags');
        const tags = Array.from(tagsSelect.selectedOptions).map(x => x.value);
        const introduction = document.getElementById('articleIntroduction').value;
        const newTag = document.getElementById('articleExtraTags').value;

        let markdown = '';
        let content = '';
        if (this.cherryInstance) {
            markdown = this.cherryInstance.getMarkdown();
            content = this.cherryInstance.getHtml();
        } else {
            const txt = document.getElementById('txt_articleContent');
            if (txt) {
                markdown = txt.value;
                content = markdown;
            }
        }

        if (title.trim() === '') {
            this.showMessage('标题不能为空', 'warning');
            return;
        }

        if (newTag.trim() === '' && tags.length === 0) {
            this.showMessage('请选择标签或输入补充标签', 'warning');
            return;
        }
        if (introduction.trim() === '') {
            this.showMessage('简介不能为空', 'warning');
            return;
        }
        if (markdown.trim() === '' || markdown.length < 5) {
            this.showMessage('内容不能为空', 'warning');
            return;
        }

        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);
        formData.append("newTag", newTag);

        try {
            const response = await fetch(action, {method: 'POST', body: formData});
            const result = await response.json();
            if (!result.success) {
                this.showMessage(result.msg || '发布失败', 'error');
                return;
            }
            this.showMessage('文章发布成功', 'success');
            $.pjax({url: '/member/articles', container: '#pjax-container'});
        } catch (error) {
            this.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.cherryInstance) {
            content = this.cherryInstance.getMarkdown();
            html = this.cherryInstance.getHtml();
        } else {
            const el = document.getElementById('mumbleContent');
            content = el ? el.value : '';
        }

        if (!content || content.trim() === '' || content.length < 5) {
            this.showMessage('请输入内容', 'warning');
            return;
        }

        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) {
                this.showMessage(result.msg || '发布失败', 'error');
                return;
            }
            this.showMessage('碎碎念发布成功', 'success');
            $.pjax({url: '/member/mumbles', container: '#pjax-container'});
        } catch (error) {
            this.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.cherryInstance) {
            content = this.cherryInstance.getMarkdown();
        } else {
            const el = document.getElementById('timelineContent');
            content = el ? el.value : '';
        }

        if (title.trim() === '') {
            this.showMessage('标题不能为空', 'warning');
            return;
        }
        if (date.trim() === '') {
            this.showMessage('时间不能为空', 'warning');
            return;
        }
        if (content.trim() === '' || content.length < 5) {
            this.showMessage('内容不能为空', 'warning');
            return;
        }

        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) {
                this.showMessage(result.msg || '发布失败', 'error');
                return;
            }
            this.showMessage('时间轴发布成功', 'success');
            $.pjax({url: '/member/timeline', container: '#pjax-container'});
        } catch (error) {
            this.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) {
            this.showMessage('请选择照片', 'warning');
            return;
        }

        const tagsSelect = document.getElementById('photoTags');
        const tags = Array.from(tagsSelect.selectedOptions).map(x => x.value).join(',');
        const newTag = document.getElementById('photoExtraTags').value;
        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);

        let finalTags = tags;
        if (newTag) {
            finalTags = finalTags ? (finalTags + ',' + newTag) : newTag;
        }
        formData.append("tags", finalTags);

        try {
            // const response = await fetch(action, { method: 'POST', body: formData });
            // const result = await response.json();

            this.showProgress('正在上传照片...');
            const result = await this.uploadWithProgress(action, formData, (percent) => {
                this.updateProgress(percent, '正在上传照片...');
            });

            if (!result.success) {
                this.showMessage(result["msg"] || '上传失败', 'error');
                return;
            }
            this.showMessage('照片上传成功', 'success');
            $.pjax({url: '/member/photos', container: '#pjax-container'});
        } catch (error) {
            this.showMessage('上传失败: ' + error.message, 'error');
        } finally {
            this.hideProgress(); // 确保 loading 被隐藏
        }
    }

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

    showProgress(message = '上传中...') {
        const $overlay = $(CONSTANTS.SELECTORS.progressOverlay);
        if ($overlay.length > 0) {
            $overlay.find('#progressText').text(message);
            $overlay.find('#progressBar').css('width', '0%');
            $overlay.find('#progressPercent').text('0%');
            $overlay.css('display', 'flex').addClass('active');
        }
    }

    updateProgress(percent, message) {
        const $overlay = $(CONSTANTS.SELECTORS.progressOverlay);
        if ($overlay.length > 0) {
            if (message) $overlay.find('#progressText').text(message);
            $overlay.find('#progressBar').css('width', `${percent}%`);
            $overlay.find('#progressPercent').text(`${percent}%`);
        }
    }

    hideProgress() {
        const $overlay = $(CONSTANTS.SELECTORS.progressOverlay);
        $overlay.removeClass('active').fadeOut(200);
    }

    showLoading(message = '加载中...') {
        const loadingText = $('.loading-spinner .loading-text');
        if (loadingText.length > 0) {
            loadingText.text(message);
        }
        $(CONSTANTS.SELECTORS.loadingOverlay).addClass('active');
    }

    hideLoading() {
        $(CONSTANTS.SELECTORS.loadingOverlay).removeClass('active');
    }

    showMessage(message, type = 'info') {
        try {
            const messageConfig = CONSTANTS.MESSAGE_TYPES;
            const config = messageConfig[type.toUpperCase()] || messageConfig.INFO;

            const messageId = `message-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;

            const alert = this.createToastElement(messageId, type, message, config);
            this.appendToastToContainer(alert);
            this.animateToast(alert, messageId);
        } catch (error) {
            console.error('显示消息失败:', error);
        }
    }

    createToastElement(messageId, type, message, config) {
        const alert = $(`
            <div class="toast-message" id="${messageId}" data-type="${type}">
                <div class="toast-content">
                    <div class="toast-icon">
                        <i class="fa ${config.icon}"></i>
                    </div>
                    <div class="toast-text">
                        <div class="toast-message-text">${message}</div>
                    </div>
                    <button type="button" class="toast-close">
                        <i class="fa fa-times"></i>
                    </button>
                </div>
                <div class="toast-progress">
                    <div class="toast-progress-bar"></div>
                </div>
            </div>
        `);

        alert.css({
            '--toast-color': config.color,
            '--toast-bg-color': config.bgColor,
            '--toast-border-color': config.borderColor,
            '--toast-text-color': config.textColor
        });

        return alert;
    }

    appendToastToContainer(alert) {
        let container = $(CONSTANTS.SELECTORS.toastContainer);
        if (container.length === 0) {
            container = $('<div id="toast-container"></div>');
            $('body').append(container);
        }
        container.append(alert);
    }

    animateToast(alert, messageId) {
        setTimeout(() => {
            alert.addClass('toast-show');
        }, 10);

        setTimeout(() => {
            alert.find('.toast-progress-bar').addClass('toast-progress-active');
        }, 100);

        alert.find('.toast-close').on('click', () => {
            this.hideMessage(messageId);
        });

        setTimeout(() => {
            this.hideMessage(messageId);
        }, CONSTANTS.DELAYS.TOAST_AUTO_HIDE);
    }

    hideMessage(messageId) {
        try {
            const alert = $(`#${messageId}`);
            if (alert.length === 0) return;

            alert.addClass('toast-hide');
            setTimeout(() => {
                alert.remove();

                const container = $(CONSTANTS.SELECTORS.toastContainer);
                if (container.children().length === 0) {
                    container.remove();
                }
            }, CONSTANTS.DELAYS.TOAST_HIDE_ANIMATION);
        } catch (error) {
            console.error('隐藏消息失败:', error);
        }
    }

    initTheme() {
        try {
            const savedTheme = localStorage.getItem('theme') ||
                (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
            this.setTheme(savedTheme);
        } catch (error) {
            console.error('初始化主题失败:', error);
            this.setTheme('light');
        }
    }

    setTheme(theme) {
        try {
            if (!['light', 'dark'].includes(theme)) {
                theme = 'light';
            }

            this.state.currentTheme = theme;
            $('html').attr('data-theme', theme);
            localStorage.setItem('theme', theme);
        } catch (error) {
            console.error('设置主题失败:', error);
        }
    }

    toggleMobileMenu() {
        $(CONSTANTS.SELECTORS.memberSidebar).toggleClass('active');
        $(CONSTANTS.SELECTORS.sidebarOverlay).toggleClass('active');
    }

    closeMobileMenu() {
        $(CONSTANTS.SELECTORS.memberSidebar).removeClass('active');
        $(CONSTANTS.SELECTORS.sidebarOverlay).removeClass('active');
    }

    handleResize() {
        try {
            const width = $(window).width();
            if (width > CONSTANTS.BREAKPOINTS.TABLET) {
                this.closeMobileMenu();
            }
        } catch (error) {
            console.error('处理窗口大小改变失败:', error);
        }
    }

    async withButtonLoading(btn, asyncAction) {
        const $btn = $(btn);
        const originalHtml = $btn.html();
        // const originalWidth = $btn.outerWidth();

        try {
            $btn.addClass('btn-loading');
            $btn.prop('disabled', true);
            $btn.html(`<i class="fa fa-spinner fa-spin"></i> ${originalHtml}`);

            await asyncAction();
        } finally {
            $btn.prop('disabled', false);
            $btn.removeClass('btn-loading');
            $btn.html(originalHtml);
        }
    }

    isMobile() {
        return $(window).width() <= CONSTANTS.BREAKPOINTS.MOBILE;
    }

    /**
     * 初始化照片查看器
     */
    initPhotoViewer() {
        try {
            const photoPreview = document.getElementById('photoPreview');
            if (!photoPreview) return;

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

                    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>';
                        }
                    };
                }
            } else if (photoPreview.src && photoPreview.src !== '') {
                // 没有加载占位符,直接初始化查看器
                this.initPhotoSwipe(photoPreview);
            }

        } catch (error) {
            console.error('初始化照片查看器失败:', error);
        }
    }

    /**
     * 初始化 PhotoSwipe 实例
     */
    initPhotoSwipe(photoPreview) {
        try {
            // 检查 PhotoSwipe 是否可用
            if (typeof PhotoSwipe === 'undefined' || typeof PhotoSwipeUI_Default === 'undefined') {
                console.warn('PhotoSwipe 未加载');
                return;
            }

            // 点击预览图片时显示查看器
            $(photoPreview).off('click.photoswipe').on('click.photoswipe', (e) => {
                e.preventDefault();
                this.openPhotoSwipe(photoPreview);
            });

        } catch (error) {
            console.error('初始化 PhotoSwipe 实例失败:', error);
        }
    }

    /**
     * 打开 PhotoSwipe 查看器
     */
    openPhotoSwipe(imgElement) {
        try {
            const pswpElement = document.querySelectorAll('.pswp')[0];

            // 构建图片数据
            const items = [{
                src: imgElement.src,
                w: imgElement.naturalWidth || 1920,
                h: imgElement.naturalHeight || 1080,
                title: imgElement.alt || '照片预览'
            }];

            // PhotoSwipe 配置选项
            const options = {
                index: 0,
                bgOpacity: 0.85,
                showHideOpacity: true,
                loop: false,
                pinchToClose: true,
                closeOnScroll: false,
                closeOnVerticalDrag: true,
                mouseUsed: false,
                escKey: true,
                arrowKeys: true,
                history: false,
                focus: true,
                showAnimationDuration: 333,
                hideAnimationDuration: 333,
                getThumbBoundsFn: function (index) {
                    const thumbnail = imgElement;
                    const pageYScroll = window.pageYOffset || document.documentElement.scrollTop;
                    const rect = thumbnail.getBoundingClientRect();
                    return {
                        x: rect.left,
                        y: rect.top + pageYScroll,
                        w: rect.width
                    };
                }
            };

            // 初始化并打开 PhotoSwipe
            const gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
            gallery.init();

            // 保存实例引用
            this.photoViewer = gallery;

        } catch (error) {
            console.error('打开 PhotoSwipe 失败:', error);
        }
    }
}

let memberCenter = null;

$(document).ready(() => {
    try {
        memberCenter = new MemberCenter();
        window.memberCenter = memberCenter;
        console.log('个人中心初始化成功');
    } catch (error) {
        console.error('个人中心初始化失败:', error);
    }
});

$(window).on('beforeunload', () => {
    // 清理工作
});

window.addEventListener('error', (event) => {
    console.error('全局错误:', event.error);
    try {
        if (memberCenter && typeof memberCenter.showMessage === 'function') {
            memberCenter.showMessage('系统出现错误,请刷新页面', 'error');
        }
    } catch (e) {
    }
});
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

下面是对你提供的这段个人中心(MemberCenter)JavaScript 代码的逐项说明,按职责模块化地解释主要功能、关键方法、事件流和依赖。为便于理解,我把内容分成概览、初始化流程、主要模块、辅助方法与注意点几部分。

一、总体概览

  • 这是一个面向单页/局部刷新的“个人中心”前端类 MemberCenter,使用 jQuery 与 PJAX 做部分页面加载、并集成了富文本/Markdown 编辑器、图片查看器、上传进度、消息提示、响应式侧栏等常见功能。
  • 使用了常量对象 CONSTANTS 保存页面类型、分页、断点、延迟、消息样式与常用 DOM 选择器,便于维护与复用。
  • 依赖(显式或隐式):jQuery、jquery-pjax、Cherry(编辑器)、PhotoSwipe、PhotoSwipeUI_Default、showdown(markdown 转 html,可选)、Prism(代码高亮,可选)、可能的 AudioRecorder。还使用浏览器原生 fetch、FormData、XMLHttpRequest(用于上传进度)。

二、初始化流程

  • 页面就绪时($(document).ready),创建全局 memberCenter 实例并绑定到 window。
  • MemberCenter.constructor 初始化 state(当前页面、主题、待删除项等),并调用 initialize()。
  • initialize() 依次调用:
    • bindEvents():绑定各种事件(移动菜单、窗口 resize、内容交互、模态框、查看内容、键盘、侧栏激活等)。
    • initPjax():配置并绑定 PJAX 行为与加载覆盖层、以及初始化上传进度 DOM(会在 body 中追加 id 为 progressOverlay 的进度弹窗)。
    • initTheme():从 localStorage 或系统偏好读取主题并设置(light/dark)。
    • initMarkdownEditor():如果当前页面包含编辑器元素,初始化 Cherry 编辑器并隐藏原 textarea。
    • initPhotoViewer():初始化图片预览/查看器(PhotoSwipe)。
    • 如果页面包含 #startRecording,则 initAudioRecorder() 初始化音频录制器(如果全局存在 AudioRecorder)。

三、PJAX 与页面导航

  • initPjax():
    • 设置 $.pjax.defaults.timeout。
    • 绑定主导航、创建新内容按钮、列表分页链接到不同的容器(#pjax-container、#member-article、#member-mumble、#member-timeline、#member-photo),实现无刷新局部加载。
    • 监听 pjax:send / pjax:complete / pjax:end 事件:显示/隐藏 loading overlay,加载完成后重新初始化编辑器、音频、图片查看器并更新页面标题,移动端则自动关闭侧栏菜单。
    • 在 body 中追加上传进度 overlay(代码中实际上有重复追加的块——见注意点)。

四、编辑器与文件上传

  • initMarkdownEditor():
    • 查找 articleContent / mumbleContent / timelineContent 三个编辑器元素之一,使用对应的隐藏 textarea(id 为 txt_ + editor.id)作为初始值。
    • 使用 Cherry 编辑器实例(this.cherryInstance)初始化,配置主题、工具栏、fileUpload 回调(调用 memberCenter.fileUpload)。
    • 隐藏原 textarea,切换到 editOnly 模式。
  • fileUpload(file, callback, uploadUrl):
    • 调用 uploadWithProgress(基于 XMLHttpRequest 的上传进度监听),显示进度覆盖层并把上传结果(result.url)传回编辑器回调。
  • uploadWithProgress(url, formData, onProgress):
    • 使用 XMLHttpRequest 以 POST 上传并在 xhr.upload.onprogress 中回调 percent,最终解析 JSON 响应。

五、照片(Photos)管理与 PhotoSwipe

  • publishPhoto():收集表单(文件、tags、description 等),用 uploadWithProgress 上传(先 showProgress),上传完成后根据返回结果提示并用 PJAX 刷新照片列表。
  • initPhotoViewer/initPhotoSwipe/openPhotoSwipe():
    • 初始化 PhotoSwipe 点击打开逻辑,构建 items(基于 img.src、naturalWidth/naturalHeight),使用 pswp DOM(.pswp)与 PhotoSwipeUI_Default 打开查看器,并保存实例到 this.photoViewer。
  • 代码中在处理编辑页面预览时,会监听图片加载完成或失败,移除占位符并显示图片。

六、表单发布(文章/碎碎念/时间轴)流程

  • publishArticle(), publishMumble(), publishTimeline():
    • 分别收集表单字段(title、tags、markdown、introduction 等),对必填项进行校验(如标题、简介、内容长度等),用 fetch POST 表单数据到 submitAddress(页面隐藏字段),处理 JSON 响应并用 this.showMessage 显示结果、通过 PJAX 刷新对应页面列表。
    • publishArticle 会从 cherryInstance 获取 Markdown/HTML 内容(或退回原 textarea)。

七、头像上传与预览

  • handleAvatarUpload(event):获取文件,校验类型/大小(validateImageFile),previewImage(FileReader -> 预览 img src),再 uploadImage()。
  • uploadImage(file):用 fetch + FormData 提交到 /Account/UpdateAvatar,显示 loading,处理返回并提示成功/失败。

八、UI 与交互辅助函数

  • showLoading / hideLoading:控制全局 loading overlay(通过 CONSTANTS.SELECTORS.loadingOverlay 的 active 类)。
  • showProgress / updateProgress / hideProgress:控制上传进度覆盖层(progressOverlay)与进度条显示与更新。
  • showMessage / createToastElement / appendToastToContainer / animateToast / hideMessage:实现自定义的 toast 消息系统,包括不同类型(success/error/warning/info)的样式(来自 CONSTANTS.MESSAGE_TYPES),自动隐藏与手动关闭。
  • withButtonLoading(btn, asyncAction):将按钮置为 loading 状态并执行异步操作,操作完成后恢复按钮状态。
  • isMobile / toggleMobileMenu / closeMobileMenu / handleResize:移动端侧栏显示/隐藏与响应式处理。
  • getCurrentContainerId:根据当前 location.pathname 决定要刷新哪个 PJAX 容器。

九、事件绑定概览

  • 绑定移动菜单开关、侧栏覆盖层关闭。
  • 绑定窗口 resize。
  • 绑定页面内各种按钮:保存资料、退出登录、上传头像、搜索文章/碎碎念/时间轴/照片、清除搜索、发布按钮(confirmPublish)、文件选择 change 事件(照片预览、拖拽上传)、查看/移除预览按钮等。
  • 绑定模态框关闭、删除确认逻辑(deleteItem / confirmDelete,演示中为模拟删除,延迟后刷新列表)。
  • 绑定键盘(Esc 隐藏删除模态)。

十、错误处理与全局

  • initialize、各方法都用 try/catch 捕获并在控制台打印错误,同时 showMessage 提示用户(若可能)。
  • window.addEventListener('error') 全局捕获并通过 memberCenter.showMessage 提示系统错误。
  • 页面卸载 beforeunload 处预留清理逻辑(目前为空)。

十一、代码中值得注意 / 可能需要修正的点

  • progressOverlay 的 DOM 被追加了两次(在 initPjax() 里出现了重复代码块),这会导致重复元素或冲突,建议去掉重复块,确保只创建一次。
  • createToastElement 使用内联样式变量(CSS 自定义属性)但没有在代码中包含对应的样式表,使用者需保证前端 CSS 存在以正确显示 toast。
  • PhotoSwipe 的使用假定全局存在 PhotoSwipe & PhotoSwipeUI_Default,并且页面中有 .pswp DOM(必要的 PhotoSwipe DOM 结构需在页面中存在)。
  • 部分方法使用了 global variable memberCenter(例如文件上传预览事件中),若在严格模式或模块化环境中需确认作用域。
  • publishArticle/publishMumble/publishTimeline 的 id 字段读取时未做空判定(有些地方使用 ?. 有的没),整体可以统一更健壮的 null/undefined 检查。
  • load/submit 接口的返回格式期待 JSON 且包含 success 字段,后端需配合该约定。

十二、总结

  • 这份代码实现了一个比较完整的“个人中心”前端管理类,功能涵盖:PJAX 局部刷新、Markdown 编辑(含文件上传)、图片上传带进度、图片查看器、头像上传、发布文章/碎碎念/时间轴、响应式侧栏、加载与消息提示、音频录制器集成点以及统一的错误处理。结构清晰、职责分明,便于扩展与维护。只需注意少数重复 DOM 插入、第三方依赖与样式配套即可稳定运行。

如果你希望我继续做任何事情,例如:

  • 给出改进建议或重构思路(比如把重复代码抽离、增强错误处理);
  • 修复重复 progressOverlay 的代码并返回修改后的片段;
  • 为某个功能(如 toast 样式、PhotoSwipe DOM)提供配套 CSS/HTML 示例; 请告诉我你想要的方向。
评论加载中...