373 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			373 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
// 用户个人中心页面交互
 | 
						|
document.addEventListener('DOMContentLoaded', function() {
 | 
						|
    // 获取表单对象
 | 
						|
    const profileForm = document.getElementById('profileForm');
 | 
						|
    const passwordForm = document.getElementById('passwordForm');
 | 
						|
 | 
						|
    // 表单验证逻辑
 | 
						|
    if (profileForm) {
 | 
						|
        profileForm.addEventListener('submit', function(event) {
 | 
						|
            let valid = true;
 | 
						|
 | 
						|
            // 清除之前的错误提示
 | 
						|
            clearValidationErrors();
 | 
						|
 | 
						|
            // 验证邮箱
 | 
						|
            const emailField = document.getElementById('email');
 | 
						|
            if (emailField.value.trim() !== '') {
 | 
						|
                const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 | 
						|
                if (!emailPattern.test(emailField.value.trim())) {
 | 
						|
                    showError(emailField, '请输入有效的邮箱地址');
 | 
						|
                    valid = false;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // 验证手机号
 | 
						|
            const phoneField = document.getElementById('phone');
 | 
						|
            if (phoneField.value.trim() !== '') {
 | 
						|
                const phonePattern = /^1[3456789]\d{9}$/;
 | 
						|
                if (!phonePattern.test(phoneField.value.trim())) {
 | 
						|
                    showError(phoneField, '请输入有效的手机号码');
 | 
						|
                    valid = false;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            if (!valid) {
 | 
						|
                event.preventDefault();
 | 
						|
            }
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    // 密码修改表单验证
 | 
						|
    if (passwordForm) {
 | 
						|
        passwordForm.addEventListener('submit', function(event) {
 | 
						|
            let valid = true;
 | 
						|
 | 
						|
            // 清除之前的错误提示
 | 
						|
            clearValidationErrors();
 | 
						|
 | 
						|
            // 验证当前密码
 | 
						|
            const currentPasswordField = document.getElementById('current_password');
 | 
						|
            if (currentPasswordField.value.trim() === '') {
 | 
						|
                showError(currentPasswordField, '请输入当前密码');
 | 
						|
                valid = false;
 | 
						|
            }
 | 
						|
 | 
						|
            // 验证新密码
 | 
						|
            const newPasswordField = document.getElementById('new_password');
 | 
						|
            if (newPasswordField.value.trim() === '') {
 | 
						|
                showError(newPasswordField, '请输入新密码');
 | 
						|
                valid = false;
 | 
						|
            } else if (newPasswordField.value.length < 6) {
 | 
						|
                showError(newPasswordField, '密码长度至少为6个字符');
 | 
						|
                valid = false;
 | 
						|
            }
 | 
						|
 | 
						|
            // 验证确认密码
 | 
						|
            const confirmPasswordField = document.getElementById('confirm_password');
 | 
						|
            if (confirmPasswordField.value.trim() === '') {
 | 
						|
                showError(confirmPasswordField, '请确认新密码');
 | 
						|
                valid = false;
 | 
						|
            } else if (confirmPasswordField.value !== newPasswordField.value) {
 | 
						|
                showError(confirmPasswordField, '两次输入的密码不一致');
 | 
						|
                valid = false;
 | 
						|
            }
 | 
						|
 | 
						|
            if (!valid) {
 | 
						|
                event.preventDefault();
 | 
						|
            }
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    // 获取用户统计数据
 | 
						|
    fetchUserStats();
 | 
						|
 | 
						|
    // 获取用户活动记录
 | 
						|
    const activityFilter = document.getElementById('activityFilter');
 | 
						|
    if (activityFilter) {
 | 
						|
        // 初始加载
 | 
						|
        fetchUserActivities('all');
 | 
						|
 | 
						|
        // 监听过滤器变化
 | 
						|
        activityFilter.addEventListener('change', function() {
 | 
						|
            fetchUserActivities(this.value);
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    // 处理URL中的tab参数
 | 
						|
    const urlParams = new URLSearchParams(window.location.search);
 | 
						|
    const tabParam = urlParams.get('tab');
 | 
						|
    if (tabParam) {
 | 
						|
        const tabElement = document.getElementById(`${tabParam}-tab`);
 | 
						|
        if (tabElement) {
 | 
						|
            $('#profileTabs a[href="#' + tabParam + '"]').tab('show');
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // 清除表单验证错误
 | 
						|
    function clearValidationErrors() {
 | 
						|
        const invalidFields = document.querySelectorAll('.is-invalid');
 | 
						|
        const feedbackElements = document.querySelectorAll('.invalid-feedback');
 | 
						|
 | 
						|
        invalidFields.forEach(field => {
 | 
						|
            field.classList.remove('is-invalid');
 | 
						|
        });
 | 
						|
 | 
						|
        feedbackElements.forEach(element => {
 | 
						|
            element.parentNode.removeChild(element);
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    // 显示错误消息
 | 
						|
    function showError(field, message) {
 | 
						|
        field.classList.add('is-invalid');
 | 
						|
 | 
						|
        const feedback = document.createElement('div');
 | 
						|
        feedback.className = 'invalid-feedback';
 | 
						|
        feedback.innerText = message;
 | 
						|
 | 
						|
        field.parentNode.appendChild(feedback);
 | 
						|
    }
 | 
						|
 | 
						|
    // 获取用户统计数据
 | 
						|
    function fetchUserStats() {
 | 
						|
        fetch('/user/api/stats')
 | 
						|
        .then(response => {
 | 
						|
            if (!response.ok) {
 | 
						|
                throw new Error('Network response was not ok');
 | 
						|
            }
 | 
						|
            return response.json();
 | 
						|
        })
 | 
						|
        .then(data => {
 | 
						|
            updateUserStats(data);
 | 
						|
        })
 | 
						|
        .catch(error => {
 | 
						|
            console.error('Error fetching user stats:', error);
 | 
						|
            // 出错时使用默认值
 | 
						|
            updateUserStats({borrow: 0, returned: 0, overdue: 0});
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    // 更新用户统计显示
 | 
						|
    function updateUserStats(data) {
 | 
						|
        const borrowCount = document.getElementById('borrowCount');
 | 
						|
        const returnedCount = document.getElementById('returnedCount');
 | 
						|
        const overdueCount = document.getElementById('overdueCount');
 | 
						|
 | 
						|
        if (borrowCount) borrowCount.textContent = data.borrow;
 | 
						|
        if (returnedCount) returnedCount.textContent = data.returned;
 | 
						|
        if (overdueCount) overdueCount.textContent = data.overdue;
 | 
						|
    }
 | 
						|
 | 
						|
    // 获取用户活动记录
 | 
						|
    function fetchUserActivities(type) {
 | 
						|
        const timelineContainer = document.getElementById('activityTimeline');
 | 
						|
        if (!timelineContainer) return;
 | 
						|
 | 
						|
        // 显示加载中
 | 
						|
        timelineContainer.innerHTML = `
 | 
						|
            <div class="timeline-loading">
 | 
						|
                <div class="spinner-border text-primary" role="status">
 | 
						|
                    <span class="sr-only">Loading...</span>
 | 
						|
                </div>
 | 
						|
                <p>加载中...</p>
 | 
						|
            </div>
 | 
						|
        `;
 | 
						|
 | 
						|
        fetch(`/user/api/activities?type=${type}`)
 | 
						|
        .then(response => {
 | 
						|
            if (!response.ok) {
 | 
						|
                throw new Error('Network response was not ok');
 | 
						|
            }
 | 
						|
            return response.json();
 | 
						|
        })
 | 
						|
        .then(data => {
 | 
						|
            renderActivityTimeline(data.activities, timelineContainer);
 | 
						|
        })
 | 
						|
        .catch(error => {
 | 
						|
            console.error('Error fetching activities:', error);
 | 
						|
            timelineContainer.innerHTML = '<div class="text-center p-4">获取活动记录失败</div>';
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    // 渲染活动时间线
 | 
						|
    function renderActivityTimeline(activities, container) {
 | 
						|
        if (!activities || activities.length === 0) {
 | 
						|
            container.innerHTML = '<div class="text-center p-4">暂无活动记录</div>';
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        let timelineHTML = '';
 | 
						|
 | 
						|
        activities.forEach((activity, index) => {
 | 
						|
            let iconClass = 'fas fa-info';
 | 
						|
 | 
						|
            if (activity.type === 'login') {
 | 
						|
                iconClass = 'fas fa-sign-in-alt';
 | 
						|
            } else if (activity.type === 'borrow') {
 | 
						|
                iconClass = 'fas fa-book';
 | 
						|
            } else if (activity.type === 'return') {
 | 
						|
                iconClass = 'fas fa-undo';
 | 
						|
            } else if (activity.type === 'other') {
 | 
						|
                iconClass = 'fas fa-cog';
 | 
						|
            }
 | 
						|
 | 
						|
            const isLast = index === activities.length - 1;
 | 
						|
 | 
						|
            timelineHTML += `
 | 
						|
                <div class="timeline-item ${isLast ? 'last' : ''} timeline-type-${activity.type}">
 | 
						|
                    <div class="timeline-icon">
 | 
						|
                        <i class="${iconClass}"></i>
 | 
						|
                    </div>
 | 
						|
                    <div class="timeline-content">
 | 
						|
                        <div class="timeline-header">
 | 
						|
                            <h5 class="timeline-title">${activity.title}</h5>
 | 
						|
                            <div class="timeline-time">${activity.time}</div>
 | 
						|
                        </div>
 | 
						|
                        <div class="timeline-details">
 | 
						|
                            ${activity.details}
 | 
						|
                            ${activity.ip ? `<div class="text-muted small">IP: ${activity.ip}</div>` : ''}
 | 
						|
                            ${activity.target_type && activity.target_id ? `
 | 
						|
                                <div class="text-muted small">
 | 
						|
                                    ${activity.target_type === 'book' ? '图书ID: ' : '目标ID: '}${activity.target_id}
 | 
						|
                                </div>` : ''}
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            `;
 | 
						|
        });
 | 
						|
 | 
						|
        container.innerHTML = timelineHTML;
 | 
						|
    }
 | 
						|
 | 
						|
    // 添加分页加载更多功能
 | 
						|
    let currentPage = 1;
 | 
						|
 | 
						|
    function loadMoreActivities(type) {
 | 
						|
        currentPage++;
 | 
						|
        const timelineContainer = document.getElementById('activityTimeline');
 | 
						|
 | 
						|
        // 显示加载中指示器
 | 
						|
        const loadingIndicator = document.createElement('div');
 | 
						|
        loadingIndicator.className = 'text-center mt-3 mb-3';
 | 
						|
        loadingIndicator.id = 'loadingMoreIndicator';
 | 
						|
        loadingIndicator.innerHTML = `
 | 
						|
            <div class="spinner-border spinner-border-sm text-primary" role="status">
 | 
						|
                <span class="sr-only">Loading...</span>
 | 
						|
            </div>
 | 
						|
            <span class="ml-2">加载更多...</span>
 | 
						|
        `;
 | 
						|
 | 
						|
        timelineContainer.appendChild(loadingIndicator);
 | 
						|
 | 
						|
        fetch(`/user/api/activities?type=${type}&page=${currentPage}`)
 | 
						|
        .then(response => {
 | 
						|
            if (!response.ok) {
 | 
						|
                throw new Error('Network response was not ok');
 | 
						|
            }
 | 
						|
            return response.json();
 | 
						|
        })
 | 
						|
        .then(data => {
 | 
						|
            // 移除加载指示器
 | 
						|
            const indicator = document.getElementById('loadingMoreIndicator');
 | 
						|
            if (indicator) indicator.remove();
 | 
						|
 | 
						|
            if (data.activities && data.activities.length > 0) {
 | 
						|
                // 添加新活动到现有时间线
 | 
						|
                appendActivitiesToTimeline(data.activities, timelineContainer);
 | 
						|
 | 
						|
                // 如果已经是最后一页,隐藏加载更多按钮
 | 
						|
                if (data.current_page >= data.pages) {
 | 
						|
                    const loadMoreBtn = document.getElementById('loadMoreBtn');
 | 
						|
                    if (loadMoreBtn) loadMoreBtn.style.display = 'none';
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                // 没有更多活动
 | 
						|
                const noMoreDiv = document.createElement('div');
 | 
						|
                noMoreDiv.className = 'text-center p-3 text-muted';
 | 
						|
                noMoreDiv.textContent = '没有更多活动记录';
 | 
						|
                timelineContainer.appendChild(noMoreDiv);
 | 
						|
 | 
						|
                const loadMoreBtn = document.getElementById('loadMoreBtn');
 | 
						|
                if (loadMoreBtn) loadMoreBtn.style.display = 'none';
 | 
						|
            }
 | 
						|
        })
 | 
						|
        .catch(error => {
 | 
						|
            console.error('Error loading more activities:', error);
 | 
						|
            // 移除加载指示器
 | 
						|
            const indicator = document.getElementById('loadingMoreIndicator');
 | 
						|
            if (indicator) indicator.remove();
 | 
						|
 | 
						|
            // 显示错误消息
 | 
						|
            const errorDiv = document.createElement('div');
 | 
						|
            errorDiv.className = 'text-center p-3 text-danger';
 | 
						|
            errorDiv.textContent = '加载更多活动失败';
 | 
						|
            timelineContainer.appendChild(errorDiv);
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    // 将新活动附加到现有时间线
 | 
						|
    function appendActivitiesToTimeline(activities, container) {
 | 
						|
        // 移除之前的 "last" 类
 | 
						|
        const lastItems = container.querySelectorAll('.timeline-item.last');
 | 
						|
        lastItems.forEach(item => {
 | 
						|
            item.classList.remove('last');
 | 
						|
        });
 | 
						|
 | 
						|
        activities.forEach((activity, index) => {
 | 
						|
            let iconClass = 'fas fa-info';
 | 
						|
 | 
						|
            if (activity.type === 'login') {
 | 
						|
                iconClass = 'fas fa-sign-in-alt';
 | 
						|
            } else if (activity.type === 'borrow') {
 | 
						|
                iconClass = 'fas fa-book';
 | 
						|
            } else if (activity.type === 'return') {
 | 
						|
                iconClass = 'fas fa-undo';
 | 
						|
            } else if (activity.type === 'other') {
 | 
						|
                iconClass = 'fas fa-cog';
 | 
						|
            }
 | 
						|
 | 
						|
            const isLast = index === activities.length - 1;
 | 
						|
 | 
						|
            const timelineItem = document.createElement('div');
 | 
						|
            timelineItem.className = `timeline-item ${isLast ? 'last' : ''} timeline-type-${activity.type}`;
 | 
						|
 | 
						|
            timelineItem.innerHTML = `
 | 
						|
                <div class="timeline-icon">
 | 
						|
                    <i class="${iconClass}"></i>
 | 
						|
                </div>
 | 
						|
                <div class="timeline-content">
 | 
						|
                    <div class="timeline-header">
 | 
						|
                        <h5 class="timeline-title">${activity.title}</h5>
 | 
						|
                        <div class="timeline-time">${activity.time}</div>
 | 
						|
                    </div>
 | 
						|
                    <div class="timeline-details">
 | 
						|
                        ${activity.details}
 | 
						|
                        ${activity.ip ? `<div class="text-muted small">IP: ${activity.ip}</div>` : ''}
 | 
						|
                        ${activity.target_type && activity.target_id ? `
 | 
						|
                            <div class="text-muted small">
 | 
						|
                                ${activity.target_type === 'book' ? '图书ID: ' : '目标ID: '}${activity.target_id}
 | 
						|
                            </div>` : ''}
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            `;
 | 
						|
 | 
						|
            container.appendChild(timelineItem);
 | 
						|
        });
 | 
						|
 | 
						|
        // 如果显示了加载更多按钮,确保它在末尾
 | 
						|
        const loadMoreBtn = document.getElementById('loadMoreBtn');
 | 
						|
        if (loadMoreBtn) {
 | 
						|
            container.appendChild(loadMoreBtn);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // 添加加载更多按钮点击事件
 | 
						|
    document.body.addEventListener('click', function(e) {
 | 
						|
        if (e.target && e.target.id === 'loadMoreBtn') {
 | 
						|
            const activityFilter = document.getElementById('activityFilter');
 | 
						|
            loadMoreActivities(activityFilter ? activityFilter.value : 'all');
 | 
						|
        }
 | 
						|
    });
 | 
						|
});
 |