/** * 问吉 - 专业命理分析系统 * 表单处理模块 - 处理表单提交和数据验证 */ /** * 初始化日期选择控件 */ function initializeDateSelectors() { // 注册监听器 document.getElementById('birth_month').addEventListener('change', updateDays); document.getElementById('birth_year').addEventListener('change', updateDays); // 默认设置年份为2000年 const yearSelect = document.getElementById('birth_year'); if (yearSelect) { // 查找2000年的选项 const yearOption = Array.from(yearSelect.options).find(option => option.value == "2000"); if (yearOption) { yearSelect.value = "2000"; } } // 初始化性别选择器 document.querySelectorAll('.gender-option').forEach(option => { option.addEventListener('click', () => { document.querySelectorAll('.gender-option').forEach(opt => opt.classList.remove('selected')); option.classList.add('selected'); document.getElementById('gender').value = option.dataset.gender; }); }); // 初始化日期输入切换 const dateInputToggle = document.getElementById('dateInputToggle'); const directDateInput = document.getElementById('direct-date-input'); const selectDateInput = document.getElementById('select-date-input'); const directBirthDate = document.getElementById('direct_birth_date'); // 初始化重置按钮功能 const resetDateBtn = document.getElementById('reset-date-btn'); if (resetDateBtn) { resetDateBtn.addEventListener('click', function() { // 清空直接输入的日期 if (directBirthDate) { directBirthDate.value = ''; // 触发change事件,确保表单验证状态更新 directBirthDate.dispatchEvent(new Event('change')); } }); } if (dateInputToggle) { dateInputToggle.addEventListener('change', function() { if (this.checked) { // 切换到直接输入模式 directDateInput.style.display = 'block'; selectDateInput.style.display = 'none'; // 如果已经选择了年月日,则将其填入直接输入框 syncSelectToDirectDate(); } else { // 切换到选择模式 directDateInput.style.display = 'none'; selectDateInput.style.display = 'block'; // 如果直接输入了日期,则将其分解为年月日填入选择框 syncDirectDateToSelect(); } }); } // 直接输入日期变化时,同步到选择控件并验证 if (directBirthDate) { directBirthDate.addEventListener('input', function(e) { // 检查年份不超过4位 const dateValue = this.value; if (dateValue) { const dateParts = dateValue.split('-'); if (dateParts.length > 0 && dateParts[0].length > 4) { // 如果年份超过4位,截取前4位 const year = dateParts[0].substring(0, 4); const rest = dateParts.slice(1).join('-'); this.value = year + (rest ? '-' + rest : ''); // 显示提示信息 this.setCustomValidity('年份不能超过4位数'); // 触发校验显示 this.reportValidity(); // 延迟清除错误提示,让用户看到错误信息 setTimeout(() => { this.setCustomValidity(''); }, 2000); } } }); // 失去焦点时也进行验证 directBirthDate.addEventListener('blur', function() { // 重置验证状态 this.setCustomValidity(''); // 尝试解析日期 if (this.value) { const date = new Date(this.value); // 检查是否是有效日期 if (isNaN(date.getTime())) { this.setCustomValidity('请输入有效的日期'); this.reportValidity(); } else { // 同步到选择控件 syncDirectDateToSelect(); } } }); // 日期变化时同步 directBirthDate.addEventListener('change', syncDirectDateToSelect); } // 同步选择器日期到直接输入控件 function syncSelectToDirectDate() { const year = document.getElementById('birth_year').value; const month = document.getElementById('birth_month').value; const day = document.getElementById('birth_day').value; if (year && month && day) { // 确保月和日格式化为两位数 const formattedMonth = month.toString().padStart(2, '0'); const formattedDay = day.toString().padStart(2, '0'); const dateString = `${year}-${formattedMonth}-${formattedDay}`; directBirthDate.value = dateString; } } // 同步直接输入的日期到选择器 function syncDirectDateToSelect() { const dateValue = directBirthDate.value; if (dateValue) { try { const dateParts = dateValue.split('-'); if (dateParts.length === 3) { // 验证年份范围 const year = parseInt(dateParts[0]); if (year < 1700 || year > 2100) { directBirthDate.setCustomValidity('请输入有效的年份(1700-2100)'); directBirthDate.reportValidity(); return; } // 去除前导零 const month = parseInt(dateParts[1]).toString(); const day = parseInt(dateParts[2]).toString(); const yearSelect = document.getElementById('birth_year'); const monthSelect = document.getElementById('birth_month'); const daySelect = document.getElementById('birth_day'); if (yearSelect && monthSelect) { // 检查选择器中是否存在该年份选项 const yearOption = Array.from(yearSelect.options).find(option => option.value == year); if (!yearOption) { // 如果选择器中不存在该年份选项,则添加这个选项 const newOption = document.createElement('option'); newOption.value = year; newOption.textContent = `${year}年`; yearSelect.appendChild(newOption); console.log(`已添加年份选项: ${year}`); } yearSelect.value = year; monthSelect.value = month; // 更新日选择器中的可选值 updateDays(); // 设置日选择器值 if (daySelect) { const dayValue = parseInt(day); const daysInMonth = new Date(year, month, 0).getDate(); // 确保选择的日期不超过当月最大天数 if (dayValue > 0 && dayValue <= daysInMonth) { daySelect.value = day; } else { console.warn(`日期 ${day} 不在有效范围内`); } } } } } catch (e) { console.error('日期同步错误:', e); directBirthDate.setCustomValidity('日期格式无效'); directBirthDate.reportValidity(); } } } } /** * 根据年月更新日期选择器中的天数 */ function updateDays() { const yearSelect = document.getElementById('birth_year'); const monthSelect = document.getElementById('birth_month'); const daySelect = document.getElementById('birth_day'); // 只有当年份和月份都已选择时才更新天数 if (!yearSelect.value || !monthSelect.value) return; const year = parseInt(yearSelect.value); const month = parseInt(monthSelect.value); // 保存当前选择的天数 const currentDay = daySelect.value; // 获取指定年月的天数 const daysInMonth = new Date(year, month, 0).getDate(); // 清空现有选项 daySelect.innerHTML = ''; // 添加新的天数选项 for (let d = 1; d <= daysInMonth; d++) { const option = document.createElement('option'); option.value = d; option.textContent = `${d}日`; daySelect.appendChild(option); } // 尝试恢复之前选择的天数值(如果在有效范围内) if (currentDay && parseInt(currentDay) <= daysInMonth) { daySelect.value = currentDay; } } /** * 处理八字分析请求表单提交 * @param {HTMLFormElement} form - 表单元素 */ function handleFortuneFormSubmit(form) { if (!form) { console.error('表单元素不存在'); return; } console.log('开始处理表单提交'); // 显示加载中状态 const loadingDiv = document.querySelector('.loading'); const resultContainer = document.getElementById('resultContainer'); const analysisResultContent = document.getElementById('analysisResultContent'); const genderFeedback = document.getElementById('gender-feedback'); if (loadingDiv) loadingDiv.style.display = 'block'; if (resultContainer) { resultContainer.style.display = 'none'; resultContainer.classList.remove('show'); } if (analysisResultContent) analysisResultContent.innerHTML = ''; // 获取表单元素 const birthTimeInput = document.getElementById('birth_time'); const birthPlaceInput = document.getElementById('birth_place'); // 收集表单数据 const formData = new FormData(form); const requestData = { name: formData.get('name') || '', gender: formData.get('gender'), birth_date: '', birth_time: formData.get('birth_time'), birth_place: formData.get('birth_place') }; console.log('收集的原始表单数据:', requestData); // 获取出生日期(根据当前选择的输入模式) const dateInputToggle = document.getElementById('dateInputToggle'); if (dateInputToggle && dateInputToggle.checked) { // 直接输入模式 const directBirthDate = document.getElementById('direct_birth_date'); if (directBirthDate) { requestData.birth_date = directBirthDate.value; console.log('直接输入模式日期:', requestData.birth_date); } } else { // 选择器模式 const year = document.getElementById('birth_year')?.value; const month = document.getElementById('birth_month')?.value; const day = document.getElementById('birth_day')?.value; console.log('选择器模式日期组件:', { year, month, day }); if (year && month && day) { const formattedMonth = month.toString().padStart(2, '0'); const formattedDay = day.toString().padStart(2, '0'); requestData.birth_date = `${year}-${formattedMonth}-${formattedDay}`; console.log('格式化后的日期:', requestData.birth_date); } } // 验证必填字段 let hasError = false; let errorMessages = []; // 验证性别 if (!requestData.gender) { console.error('性别未选择'); if (genderFeedback) { genderFeedback.style.display = 'flex'; // 添加动画效果 document.querySelectorAll('.gender-option').forEach(opt => { opt.style.animation = 'genderSelectionEmphasis 1.5s infinite'; }); // 2秒后移除动画 setTimeout(() => { document.querySelectorAll('.gender-option').forEach(opt => { opt.style.animation = ''; }); }, 2000); } hasError = true; errorMessages.push('请选择性别'); } // 验证出生日期 if (!requestData.birth_date) { console.error('出生日期未填写'); hasError = true; errorMessages.push('请选择或输入出生日期'); } // 验证出生时间 if (!requestData.birth_time) { console.error('出生时间未填写'); if (birthTimeInput) { birthTimeInput.classList.add('is-invalid'); } hasError = true; errorMessages.push('请输入出生时间'); } else { if (birthTimeInput) { birthTimeInput.classList.remove('is-invalid'); } } // 验证出生地点 if (!requestData.birth_place) { console.error('出生地点未填写'); if (birthPlaceInput) { birthPlaceInput.classList.add('is-invalid'); } hasError = true; errorMessages.push('请输入出生地点'); } else { if (birthPlaceInput) { birthPlaceInput.classList.remove('is-invalid'); } } // 如果有错误,显示所有错误信息并终止提交 if (hasError) { if (loadingDiv) loadingDiv.style.display = 'none'; console.log('表单验证失败:', errorMessages); alert(errorMessages.join('\n')); return; } console.log('表单验证通过,准备发送请求,数据:', requestData); // 发送请求,添加重试功能 sendFortuneRequest(requestData, 1); // 封装请求函数,支持重试 function sendFortuneRequest(requestData, attempt) { console.log(`正在发送分析请求,尝试次数: ${attempt}`); fetch('/fortune', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify(requestData) }) .then(response => { if (!response.ok) { throw new Error('网络响应不正常'); } return response.json(); }) .then(data => { if (data.success) { // 请求成功,显示结果 if (loadingDiv) loadingDiv.style.display = 'none'; // 显示结果容器 if (resultContainer) { resultContainer.style.display = 'block'; // 添加一个短暂延迟,确保display:block生效后再添加show类 setTimeout(() => { resultContainer.classList.add('show'); // 平滑滚动到结果区域 resultContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 50); // 渲染分析结果 if (analysisResultContent) { renderFortuneResult(data, analysisResultContent); } } } else { // 请求失败,可能是暂时性错误,尝试重试 if (attempt < 2 && data.error === "获取八字数据失败") { console.log("第一次请求失败,正在重试..."); // 短暂延迟后重试请求 setTimeout(() => { sendFortuneRequest(requestData, attempt + 1); }, 1000); } else { // 重试失败或其他错误,显示错误信息 if (loadingDiv) loadingDiv.style.display = 'none'; alert(data.error || '分析请求失败,请稍后重试'); } } }) .catch(error => { console.error('分析请求错误:', error); // 网络错误时也尝试重试 if (attempt < 2) { console.log("请求出错,正在重试..."); setTimeout(() => { sendFortuneRequest(requestData, attempt + 1); }, 1000); } else { if (loadingDiv) loadingDiv.style.display = 'none'; alert('分析请求发生错误,请稍后重试'); } }); } } /** * 渲染八字分析结果 * @param {Object} data - 分析结果数据 * @param {HTMLElement} container - 结果容器元素 */ function renderFortuneResult(data, container) { if (!data || !container) { console.error('渲染结果失败:数据或容器为空'); return; } // 清空容器 container.innerHTML = ''; // 添加mobile-responsive类以确保移动响应性 container.classList.add('mobile-responsive'); // 创建结果容器 const analysisContainer = document.createElement('div'); analysisContainer.className = 'results-wrapper mobile-responsive'; // 确保容器有result-container类,这对于AI分析按钮的添加非常重要 container.classList.add('result-container'); // 如果有分析ID,将其设置到结果容器的data-analysis-id属性 if (data.analysis_id) { // 查找结果容器的父级容器(resultContainer)并设置分析ID const resultContainer = document.getElementById('resultContainer'); if (resultContainer) { resultContainer.setAttribute('data-analysis-id', data.analysis_id); console.log('设置分析ID到结果容器:', data.analysis_id); } } // 解析基本信息 const basicInfo = data.basic_info || ''; const sections = basicInfo.split('\n\n'); let formattedResult = '
'; // 处理基本信息部分 if (sections[0] && sections[0].includes('【基本信息】')) { const basicInfoLines = sections[0].split('\n').slice(1); formattedResult += generateBasicInfoHTML(basicInfoLines); } // 处理八字分析部分 if (sections[1] && sections[1].includes('【八字分析】')) { formattedResult += generateBaziAnalysisHTML(sections[1], data); } // 添加综合分析卡片 formattedResult += generateUnifiedAnalysisHTML(data); // 添加公开分享链接(如果存在分析ID) if (data.analysis_id) { formattedResult += generateShareSectionHTML(data.analysis_id); } // 添加AI深度分析部分(如果存在) if (data.ai_analysis) { formattedResult += generateAIAnalysisHTML(data.ai_analysis); } formattedResult += '
'; container.innerHTML = formattedResult; // 在结果渲染完成后初始化图表和事件 setTimeout(() => { // 初始化五行图表 if (data.wuxing_data && data.wuxing_data.elements) { renderWuxingChart(data.wuxing_data.elements, 'wuxingPieChartUnified'); } // 添加AI分析按钮(如果未包含AI分析) if (!data.ai_analysis && data.analysis_id) { addAIAnalysisButton(data, container); } // 初始化分享模态框事件 if (data.analysis_id) { initShareModal(data.analysis_id); } // 初始化工具提示 initializeTooltips(); // 初始化复制按钮 initializeCopyButtons(); }, 100); } /** * 生成基本信息HTML * @param {Array} basicInfo - 基本信息数组 * @returns {string} HTML代码 */ function generateBasicInfoHTML(basicInfo) { return `

基本信息

${basicInfo.map(line => { const colonIndex = line.indexOf(': '); if (colonIndex !== -1) { const key = line.substring(0, colonIndex); const value = line.substring(colonIndex + 2); return ``; } else { return ``; } }).join('')}
${key}${value}
${line}
`; } /** * 生成八字分析表格HTML * @param {string} baziSection - 八字分析文本 * @param {Object} data - 分析数据 * @returns {string} HTML代码 */ function generateBaziAnalysisHTML(baziSection, data) { const baziLines = baziSection.split('\n'); // 获取列标题 let headers = []; if (baziLines[1] && baziLines[1].trim()) { headers = baziLines[1].trim().split(/\s+/); } // 解析八字行数据 const rows = parseBaziRows(baziLines); // 处理神煞数据 const shenShaData = processShenShaData(data); const allShenShaEmpty = checkAllShenShaEmpty(shenShaData); // 生成八字分析表格HTML if (allShenShaEmpty) { return generateBaziTableWithoutShenSha(headers, rows); } else { return generateBaziTableWithShenSha(headers, rows, shenShaData); } } /** * 解析八字行数据 * @param {Array} baziLines - 八字文本行数组 * @returns {Array} 行数据数组 */ function parseBaziRows(baziLines) { const rows = []; let currentRow = null; let currentValues = null; for (let i = 2; i < baziLines.length; i++) { const line = baziLines[i] ? baziLines[i].trim() : ''; if (line.includes('天干') || line.includes('地支') || line.includes('纳音') || line.includes('星运') || line.includes('自坐') || line.includes('空亡')) { if (currentRow && currentValues) { rows.push({ name: currentRow, values: currentValues }); } currentRow = line; currentValues = null; } else if (line && currentRow) { // 检查是否是五行分析部分的开始 if (line.includes('【五行分析】')) { break; } currentValues = line.split(/\s+/); if (currentValues.length > 0) { rows.push({ name: currentRow, values: currentValues }); currentRow = null; currentValues = null; } } } return rows; } /** * 处理神煞数据 * @param {Object} data - 分析数据 * @returns {Object} 处理后的神煞数据 */ function processShenShaData(data) { return { yearGanZhiShenSha: Array.isArray(data.yearGanZhiShenSha) ? data.yearGanZhiShenSha : (data.yearGanZhiShenSha ? [data.yearGanZhiShenSha] : []), monthGanZhiShenSha: Array.isArray(data.monthGanZhiShenSha) ? data.monthGanZhiShenSha : (data.monthGanZhiShenSha ? [data.monthGanZhiShenSha] : []), dayGanZhiShenSha: Array.isArray(data.dayGanZhiShenSha) ? data.dayGanZhiShenSha : (data.dayGanZhiShenSha ? [data.dayGanZhiShenSha] : []), hourGanZhiShenSha: Array.isArray(data.hourGanZhiShenSha) ? data.hourGanZhiShenSha : (data.hourGanZhiShenSha ? [data.hourGanZhiShenSha] : []) }; } /** * 检查所有神煞数据是否为空 * @param {Object} shenShaData - 神煞数据对象 * @returns {boolean} 是否全为空 */ function checkAllShenShaEmpty(shenShaData) { return shenShaData.yearGanZhiShenSha.length === 0 && shenShaData.monthGanZhiShenSha.length === 0 && shenShaData.dayGanZhiShenSha.length === 0 && shenShaData.hourGanZhiShenSha.length === 0; } /** * 生成没有神煞数据的八字表格HTML * @param {Array} headers - 表格头部 * @param {Array} rows - 行数据 * @returns {string} HTML代码 */ function generateBaziTableWithoutShenSha(headers, rows) { // 创建表格容器,确保表格可以水平滚动 const tableContainer = document.createElement('div'); tableContainer.className = 'bazi-table-container table-responsive'; // 创建表格 const table = document.createElement('table'); table.className = 'bazi-table'; // 添加表头 table.innerHTML = ` ${headers.map(h => `${h}`).join('')} ${rows.map(row => ` ${row.name} ${row.values.map(v => `${v}`).join('')} `).join('')} `; // 添加到表格容器 tableContainer.appendChild(table); return tableContainer.outerHTML; } /** * 生成包含神煞数据的八字表格HTML * @param {Array} headers - 表格头部 * @param {Array} rows - 行数据 * @param {Object} shenShaData - 神煞数据 * @returns {string} HTML代码 */ function generateBaziTableWithShenSha(headers, rows, shenShaData) { // 创建表格容器,确保表格可以水平滚动 const tableContainer = document.createElement('div'); tableContainer.className = 'bazi-table-container table-responsive'; // 创建表格 const table = document.createElement('table'); table.className = 'bazi-table'; // 添加表头和基本行 let tableHTML = ` ${headers.map(h => `${h}`).join('')} ${rows.map(row => ` ${row.name} ${row.values.map(v => `${v}`).join('')} `).join('')} 神煞 ${['yearGanZhiShenSha', 'monthGanZhiShenSha', 'dayGanZhiShenSha', 'hourGanZhiShenSha'].map((key, index) => { const columnLabels = ['年柱', '月柱', '日柱', '时柱']; const shenShaArray = shenShaData[key] || []; console.log(`渲染${columnLabels[index]}神煞:`, shenShaArray); return `
${shenShaArray.length > 0 ? `
${shenShaArray.map(s => { let type = 'gui'; for (const [pattern, value] of Object.entries(SHENSHA_TYPES)) { if (s.includes(pattern)) { type = value; break; } } return `
${s}
`; }).join('')}
` : '无' }
`; }).join('')} `; table.innerHTML = tableHTML; // 添加到表格容器 tableContainer.appendChild(table); return tableContainer.outerHTML; } /** * 生成综合分析HTML * @param {Object} data - 分析数据 * @returns {string} HTML代码 */ function generateUnifiedAnalysisHTML(data) { // 创建五行分布内容 let wuxingContent = ''; if (data.wuXingFenXi) { wuxingContent = `

${data.wuXingFenXi.replace(/\n/g, '
')}

`; } // 提取并处理骨重数据 let guzhongContent = generateGuzhongHTML(data); // 渲染五行图表的ID const wuxingChartId = 'wuxingPieChartUnified'; // 构建合并的分析卡片 return `

综合命理分析

五行分布

${wuxingContent}

阴阳比例

${generateYinYangAnalysisHTML(data.yinyang_data)}

骨重分析

${guzhongContent}
`; } /** * 生成骨重分析HTML * @param {Object} data - 分析数据 * @returns {string} HTML代码 */ function generateGuzhongHTML(data) { if (!data.guZhong && !data.guZhongPiZhu) return ''; const guzhongValue = data.guZhong || ''; let guzhongDesc = ''; if (data.guZhongPiZhu) { const lines = data.guZhongPiZhu.split('\n').filter(line => line.trim() !== ''); if (lines.length > 0) { // 检查是否第一行包含"骨重"文本 const firstLineMatch = lines[0].match(/骨重[::\s]+(.+)/); if (firstLineMatch) { guzhongDesc = lines.slice(1).join('
'); } else if (lines.length > 1) { guzhongDesc = lines.slice(1).join('
'); } else { guzhongDesc = lines[0]; } } } return `
${guzhongValue || '暂无数据'}
${guzhongDesc || '暂无详细分析'}
`; } /** * 生成AI深度分析HTML * @param {string} aiAnalysis - AI生成的分析文本 * @returns {string} 格式化后的HTML */ function generateAIAnalysisHTML(aiAnalysis) { // 将AI分析结果中的markdown转换为HTML,并删除第一行 let lines = aiAnalysis.split('\n'); // 优化处理第一行标题的逻辑,确保能正确处理任何级别的标题(包括###) if (lines.length > 1) { const firstLine = lines[0].trim(); // 处理所有级别的Markdown标题和空行 if (firstLine.match(/^#{1,6}\s+/) || firstLine === '') { console.log('最终分析删除标题行:', firstLine); lines = lines.slice(1); } } let cleanedAnalysis = lines.join('\n'); let formattedAnalysis = convertMarkdownToHTML(cleanedAnalysis); return `

AI深度命理分析

使用模型:${modelType === 'deep' ? '深度分析模型' : '基础分析模型'}
此分析由AI根据传统命理理论生成,仅供参考
`; } /** * 将Markdown格式文本转换为HTML * @param {string} markdown - Markdown格式文本 * @returns {string} HTML格式文本 */ function convertMarkdownToHTML(markdown) { if (!markdown) return ''; // 预处理 - 用于调试 console.log('处理Markdown转HTML,内容长度:', markdown.length); // 预处理Markdown,统一各种列表标记为减号,便于后续处理 let processedMarkdown = markdown; // 确保所有emoji保留原样 processedMarkdown = processedMarkdown.replace(/([^\u0000-\u007F]+)/g, function(match) { return match; // 直接保留所有非ASCII字符(包括emoji) }); // 统一无序列表标记 (· 和 * 转换为 -) processedMarkdown = processedMarkdown.replace(/^[·\*]\s+(.+)$/gm, '- $1'); // 确保列表项之间有换行,帮助解析 processedMarkdown = processedMarkdown.replace(/(\n[-·\*]\s.+?)(\n[-·\*]\s)/g, '$1\n$2'); // 确保标题前有空行 processedMarkdown = processedMarkdown.replace(/(\n[^#\n].+?)(\n#{1,6}\s)/g, '$1\n\n$2'); // 处理分割线 - 单独处理 --- 分隔符 processedMarkdown = processedMarkdown.replace(/^---+\s*$/gm, '
'); // 替换标题 let html = processedMarkdown // 替换六级标题 .replace(/^#{6}\s+(.+)$/gm, '
$1
') // 替换五级标题 .replace(/^#{5}\s+(.+)$/gm, '
$1
') // 替换四级标题 .replace(/^#{4}\s+(.+)$/gm, '

$1

') // 替换三级标题 - 移除分隔线,直接使用h4标题 .replace(/^#{3}\s+(.+)$/gm, '

$1

') // 替换二级标题 .replace(/^#{2}\s+(.+)$/gm, '

$1

') // 替换一级标题 .replace(/^#\s+(.+)$/gm, '

$1

'); // 处理表格 (Markdown表格格式: | Header1 | Header2 | 和 | ------- | ------- |) // 更新的正则表达式,适应可能没有尾部换行符的表格格式 const tableRegex = /\|(.+)\|\r?\n\|([\s\-:]+\|)+\r?\n((?:\|.+\|\r?\n)*(?:\|.+\|)?)/g; html = html.replace(tableRegex, function(match) { try { console.log('找到表格:', match); // 按行分割 const lines = match.split(/\r?\n/).filter(line => line.trim() !== ''); if (lines.length < 3) { console.error('表格行数不足:', lines); return match; } // 提取表格头 const headerLine = lines[0]; const headers = headerLine.split('|') .filter(cell => cell !== '') .map(cell => cell.trim()); // 提取表格内容 const contentLines = lines.slice(2); const rows = contentLines.map(line => { return line.split('|') .filter(cell => cell !== '') .map(cell => cell.trim()); }); console.log('解析表格: 标题=', headers, '行=', rows); // 构建HTML表格 let tableHtml = '
'; // 添加表头 tableHtml += ''; headers.forEach(header => { tableHtml += ``; }); tableHtml += ''; // 添加表格内容 tableHtml += ''; rows.forEach(row => { if (row.length > 0) { tableHtml += ''; row.forEach(cell => { // 保护单元格内容,不对表格内的emoji进行CSS转换 tableHtml += ``; }); tableHtml += ''; } }); tableHtml += '
${header}
${cell}
'; console.log('生成的HTML表格:', tableHtml); return tableHtml; } catch (e) { console.error('表格解析失败:', e, '原始内容:', match); return match; // 如果解析失败,保留原始内容 } }); // 处理特殊标记:✦ xxx ✦ html = html.replace(/✦\s+(.+?)\s+✦/g, '
$1
'); // 新的无序列表处理方法 - 使用更简单的正则表达式全局匹配 html = html.replace(/(?:^|\n)(?:[-*·]\s+.+\n?)+/g, function(match) { try { const items = match.split('\n') .filter(line => line.trim() !== '') .map(line => line.trim().replace(/^[-*·]\s+/, '')) .filter(item => item !== ''); if (items.length === 0) return match; let listHtml = ''; return listHtml; } catch (e) { console.error('列表解析失败:', e); return match; // 如果解析失败,保留原始内容 } }); // 处理有序列表 html = html.replace(/(?:^|\n)(?:\d+\.\s+.+\n?)+/g, function(match) { try { const items = match.split('\n') .filter(line => line.trim() !== '') .map(line => line.trim().replace(/^\d+\.\s+/, '')) .filter(item => item !== ''); if (items.length === 0) return match; let listHtml = '
    '; items.forEach(item => { listHtml += `
  1. ${item}
  2. `; }); listHtml += '
'; return listHtml; } catch (e) { console.error('有序列表解析失败:', e); return match; // 如果解析失败,保留原始内容 } }); // 替换粗体 html = html.replace(/\*\*(.+?)\*\*/g, '$1'); // 替换斜体 html = html.replace(/\*(.+?)\*/g, '$1'); // 替换引用 html = html.replace(/^>\s(.+)$/gm, '
$1
'); // 替换特殊emoji标记为更美观的HTML元素 html = processEmojiAndSpecialMarkers(html); // 替换换行符为
标签 html = html.replace(/\n/g, '
'); // 特殊处理一些可能缺少的Font Awesome图标 html = html.replace(/$1' }, { pattern: /⚠️\s(.+)/g, replacement: '
$1
' }, { pattern: /✅\s(.+)/g, replacement: '
$1
' }, { pattern: /⚖️\s(.+)/g, replacement: '
$1
' }, { pattern: /🌌\s(.+)/g, replacement: '
$1
' }, { pattern: /🚀\s(.+)/g, replacement: '
$1
' }, { pattern: /🌠\s(.+)/g, replacement: '
$1
' }, { pattern: /💎\s(.+)/g, replacement: '
$1
' }, { pattern: /🛡️\s(.+)/g, replacement: '
$1
' }, { pattern: /🔭\s(.+)/g, replacement: '
$1
' }, { pattern: /△\s(.+)/g, replacement: '
$1
' } ]; // 处理表格内外的内容 const tablePattern = /
.*?<\/div>/gs; let tableParts = []; let nonTableParts = []; let lastIndex = 0; let match; // 分离表格和非表格部分 while ((match = tablePattern.exec(html)) !== null) { nonTableParts.push(html.substring(lastIndex, match.index)); tableParts.push(match[0]); lastIndex = match.index + match[0].length; } nonTableParts.push(html.substring(lastIndex)); // 只处理非表格部分 for (let i = 0; i < nonTableParts.length; i++) { let part = nonTableParts[i]; // 应用所有特殊标记替换 for (const marker of specialMarkers) { part = part.replace(marker.pattern, marker.replacement); } nonTableParts[i] = part; } // 重新组合文档 let result = ''; for (let i = 0; i < nonTableParts.length; i++) { result += nonTableParts[i]; if (i < tableParts.length) { result += tableParts[i]; } } return result; } /** * 生成分享部分HTML * @param {string} analysisId - 分析ID * @returns {string} HTML代码 */ function generateShareSectionHTML(analysisId) { return `
您可以选择公开或私密方式分享此分析
`; } /** * 显示分享选项 * @param {string} analysisId - 分析ID */ function showShareOptions(analysisId) { if (!analysisId) { console.error('分析ID为空,无法继续分享操作'); alert('无法生成分享链接,缺少必要的分析ID'); return; } console.log('准备显示分享模态窗口,分析ID:', analysisId); // 创建模态框 const modalHtml = ` `; // 删除可能已存在的模态框 const existingModal = document.getElementById('shareModal'); if (existingModal) { console.log('删除已存在的分享模态窗口'); existingModal.remove(); } // 添加模态框到页面 document.body.insertAdjacentHTML('beforeend', modalHtml); console.log('分享模态窗口HTML已添加到页面'); // 获取模态框元素 const modal = document.getElementById('shareModal'); if (!modal) { console.error('无法获取分享模态窗口元素'); alert('无法显示分享窗口,请刷新页面后重试'); return; } // 确保Bootstrap已加载 if (typeof bootstrap === 'undefined') { console.error('Bootstrap未加载,无法初始化模态窗口'); // 尝试动态加载Bootstrap const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js'; script.onload = function() { console.log('Bootstrap已动态加载'); initializeShareModal(modal, analysisId); }; script.onerror = function() { console.error('无法加载Bootstrap'); alert('无法加载必要的组件,请检查网络连接后重试'); }; document.head.appendChild(script); return; } // 初始化模态窗口 initializeShareModal(modal, analysisId); } /** * 初始化分享模态窗口 * @param {HTMLElement} modal - 模态窗口元素 * @param {string} analysisId - 分析ID */ function initializeShareModal(modal, analysisId) { try { console.log('初始化Bootstrap模态窗口'); // 确保QRCode库已加载 if (typeof QRCode === 'undefined') { console.log('QRCode库未加载,正在加载...'); const qrScript = document.createElement('script'); qrScript.src = 'https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js'; document.head.appendChild(qrScript); } const modalInstance = new bootstrap.Modal(modal); modalInstance.show(); console.log('分享模态窗口已显示'); // 绑定事件 initShareModalEvents(analysisId); } catch (error) { console.error('模态窗口初始化失败:', error); alert('显示分享窗口失败,请刷新页面后重试'); } } /** * 初始化分享模态框事件 * @param {string} analysisId - 分析ID */ function initShareModalEvents(analysisId) { console.log('初始化分享模态窗口事件,分析ID:', analysisId); // 隐私选项卡事件 document.querySelectorAll('.privacy-card').forEach(card => { card.addEventListener('click', function() { // 移除其他卡片的active类 document.querySelectorAll('.privacy-card').forEach(c => c.classList.remove('active')); // 添加当前卡片的active类 this.classList.add('active'); // 选中当前卡片的单选按钮 const radio = this.querySelector('.privacy-card-radio'); if (radio) radio.checked = true; }); }); // 生成链接按钮事件 const generateBtn = document.getElementById('generateLinkBtn'); if (generateBtn) { generateBtn.addEventListener('click', function() { // 获取选中的分享模式 const selectedOption = document.querySelector('.privacy-card-radio:checked'); if (!selectedOption) { alert('请选择分享方式'); return; } const privacy = selectedOption.value; // 显示加载状态 this.innerHTML = '生成中...'; this.disabled = true; // 格式化分析ID let formattedId = analysisId; if (analysisId.length === 32 && !analysisId.includes('-')) { formattedId = `${analysisId.substring(0, 8)}-${analysisId.substring(8, 12)}-${analysisId.substring(12, 16)}-${analysisId.substring(16, 20)}-${analysisId.substring(20, 32)}`; } console.log('调用API生成分享链接,分析ID:', formattedId, '分享模式:', privacy); console.log('请求URL:', '/api/share/' + formattedId); // 调用后端API生成分享链接 fetch('/api/share/' + formattedId, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify({ mode: privacy }) }) .then(response => { if (!response.ok) { throw new Error('网络响应不正常'); } return response.json(); }) .then(data => { console.log('分享链接生成结果:', data); if (data.success) { // 更新链接输入框 const shareUrlInput = document.getElementById('shareUrlInput'); if (shareUrlInput) shareUrlInput.value = data.url; // 切换显示步骤 document.getElementById('shareStep1').style.display = 'none'; document.getElementById('shareStep2').style.display = 'block'; // 更新隐私指示器 if (privacy === 'private') { document.getElementById('privateIndicator').style.display = 'inline'; document.getElementById('publicIndicator').style.display = 'none'; } else { document.getElementById('privateIndicator').style.display = 'none'; document.getElementById('publicIndicator').style.display = 'inline'; } // 生成二维码 setTimeout(() => { try { const qrcodeContainer = document.getElementById('qrcodeContainer'); if (qrcodeContainer) { qrcodeContainer.innerHTML = ''; if (typeof QRCode !== 'undefined') { console.log('生成二维码,URL:', data.url); new QRCode(qrcodeContainer, { text: data.url, width: 150, height: 150, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H }); } else { console.error('QRCode库未加载'); qrcodeContainer.innerHTML = '
二维码库未加载,请刷新页面重试
'; } } } catch(e) { console.error('生成二维码失败:', e); } }, 300); } else { alert(data.error || '生成分享链接失败'); } }) .catch(error => { console.error('请求分享链接失败:', error); alert('生成分享链接失败,请稍后再试'); }) .finally(() => { // 恢复按钮状态 this.innerHTML = '生成分享链接'; this.disabled = false; }); }); } // 返回按钮事件 const backBtn = document.getElementById('backToOptionsBtn'); if (backBtn) { backBtn.addEventListener('click', function() { document.getElementById('shareStep1').style.display = 'block'; document.getElementById('shareStep2').style.display = 'none'; }); } // 复制链接按钮事件 const copyBtn = document.getElementById('copyLinkBtn'); if (copyBtn) { copyBtn.addEventListener('click', function() { const shareUrlInput = document.getElementById('shareUrlInput'); if (shareUrlInput && shareUrlInput.value) { shareUrlInput.select(); try { // 尝试使用现代API if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(shareUrlInput.value) .then(() => { // 更新按钮状态 const originalText = this.innerHTML; this.innerHTML = ' 已复制'; setTimeout(() => { this.innerHTML = originalText; }, 2000); // 显示成功提示 showCopySuccessToast(); }) .catch(err => { console.error('复制失败:', err); // 回退到传统方法 document.execCommand('copy'); // 更新按钮状态 const originalText = this.innerHTML; this.innerHTML = ' 已复制'; setTimeout(() => { this.innerHTML = originalText; }, 2000); showCopySuccessToast(); }); } else { // 回退到传统方法 document.execCommand('copy'); // 更新按钮状态 const originalText = this.innerHTML; this.innerHTML = ' 已复制'; setTimeout(() => { this.innerHTML = originalText; }, 2000); // 显示成功提示 showCopySuccessToast(); } } catch (err) { console.error('复制失败:', err); alert('复制失败,请手动复制链接'); } } else { alert('没有可供复制的链接'); } }); } // 下载二维码按钮事件 const downloadBtn = document.getElementById('downloadQRBtn'); if (downloadBtn) { downloadBtn.addEventListener('click', function() { const qrcodeContainer = document.getElementById('qrcodeContainer'); if (qrcodeContainer) { const canvas = qrcodeContainer.querySelector('canvas'); if (canvas) { try { const imgData = canvas.toDataURL('image/png'); const link = document.createElement('a'); link.href = imgData; link.download = '命理分析分享二维码.png'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } catch (error) { console.error('下载二维码失败:', error); alert('下载二维码失败,请重试'); } } else { alert('二维码尚未生成,请先生成分享链接'); } } }); } } /** * 显示复制成功提示 */ function showCopySuccessToast() { // 创建提示元素 const toast = document.createElement('div'); toast.className = 'copy-success-alert'; toast.innerHTML = ` 链接已复制到剪贴板 `; // 添加到页面 document.body.appendChild(toast); // 显示动画 setTimeout(() => { toast.style.transform = 'translateY(0)'; toast.style.opacity = '1'; // 2秒后隐藏 setTimeout(() => { toast.style.transform = 'translateY(-100px)'; toast.style.opacity = '0'; // 动画结束后移除 setTimeout(() => { if (document.body.contains(toast)) { document.body.removeChild(toast); } }, 300); }, 2000); }, 10); } /** * 请求AI分析并处理结果 * @param {Object} baziData - 八字数据 * @param {string} analysisId - 分析ID(UUID格式) * @param {boolean} useStream - 是否使用流式响应 * @param {function} progressCallback - 进度回调函数,用于实时更新内容 * @returns {Promise} 请求处理Promise */ function requestAIAnalysis(baziData, analysisId, useStream = true, progressCallback = null, modelType = 'basic') { return new Promise((resolve, reject) => { // 查找或创建结果容器 let resultDiv = document.getElementById('result'); // 如果找不到#result容器,尝试寻找其他可用容器 if (!resultDiv) { console.log('找不到#result容器,尝试寻找替代容器'); // 查找顺序:.result-container > #ai > body resultDiv = document.querySelector('.result-container'); if (!resultDiv) { resultDiv = document.getElementById('ai'); if (resultDiv) { // 添加类以便后续使用 resultDiv.classList.add('result-container'); } else { // 最后的选择:使用body resultDiv = document.body; console.log('未找到专用容器,使用body作为容器'); } } } const aiLoadingDiv = document.createElement('div'); aiLoadingDiv.className = 'ai-loading'; // 流式响应时使用不同的加载显示 if (useStream) { aiLoadingDiv.innerHTML = `
AI分析中

正在生成AI深度分析,正文将逐步显示...

`; } else { aiLoadingDiv.innerHTML = `
AI分析中

正在请求AI深度分析,这可能需要一些时间...

`; } // 查找合适的位置插入加载提示 const analysisCard = resultDiv.querySelector('.unified-analysis-card'); if (analysisCard) { analysisCard.parentNode.insertBefore(aiLoadingDiv, analysisCard.nextSibling); } else { resultDiv.appendChild(aiLoadingDiv); } // 构建请求参数 const requestData = { stream: useStream, model_type: modelType // 添加模型类型参数 }; if (analysisId) { console.log(`[调试] 准备发送AI分析请求,使用分析ID: ${analysisId} (类型: ${typeof analysisId}), 模型类型: ${modelType}`); requestData.analysis_id = analysisId; } else if (baziData) { console.log(`[调试] 准备发送AI分析请求,使用八字数据, 模型类型: ${modelType}`); requestData.bazi_data = baziData; } else { console.error('[错误] 无法获取分析数据,缺少分析ID和八字数据'); aiLoadingDiv.innerHTML = '
无法获取分析数据
'; reject('无法获取分析数据'); return; } // 打印完整的请求数据,辅助调试 console.log('[调试] 完整的AI分析请求数据:', JSON.stringify(requestData)); // 创建AI分析结果容器 const aiResultDiv = document.createElement('div'); aiResultDiv.className = 'ai-analysis-card'; aiResultDiv.innerHTML = `

AI深度命理分析

使用模型:${modelType === 'deep' ? '深度分析模型' : '基础分析模型'}
此分析由AI根据传统命理理论生成,仅供参考
`; // 插入到适当位置 if (analysisCard) { analysisCard.parentNode.insertBefore(aiResultDiv, aiLoadingDiv.nextSibling); } else { resultDiv.appendChild(aiResultDiv); } // 获取内容容器 const aiContentDiv = aiResultDiv.querySelector('.ai-analysis-content'); // 使用流式响应 if (useStream) { // 初始化EventSource接收流式响应 let contentBuffer = ''; let actualContent = ''; // 保存解析后的实际内容 let xhr = new XMLHttpRequest(); xhr.open('POST', '/ai_analysis'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.responseType = 'text'; // 处理分块响应 xhr.onprogress = function() { // 获取新内容 const newContent = xhr.responseText.substring(contentBuffer.length); contentBuffer += newContent; try { // 处理新的SSE事件 const lines = newContent.split('\n\n'); lines.forEach(line => { if (line.trim().startsWith('data:')) { const jsonStr = line.replace('data:', '').trim(); if (!jsonStr) return; // 跳过空数据 const data = JSON.parse(jsonStr); console.log('解析流数据:', data); // 处理块 if (data.chunk) { // 只提取实际内容,不要存储整个JSON if (typeof data.chunk === 'string') { // 累积实际内容 actualContent += data.chunk; // 调用进度回调函数(如果有) if (typeof progressCallback === 'function') { progressCallback(data.chunk); } // 将解析后的内容显示在页面上 aiContentDiv.innerHTML = convertMarkdownToHTML(actualContent); } else { console.warn('接收到非字符串chunk数据:', data.chunk); } // 滚动到底部 aiContentDiv.scrollTop = aiContentDiv.scrollHeight; } // 处理完成事件 if (data.done) { console.log('流式响应完成'); aiLoadingDiv.remove(); // 确保最后一次完整显示 aiContentDiv.innerHTML = convertMarkdownToHTML(actualContent); // 如果提供了重定向URL,处理重定向 if (data.redirect) { alert('AI分析已完成,即将跳转到详情页面查看'); setTimeout(() => { window.location.href = data.redirect; }, 1000); } resolve(actualContent); } // 处理错误 if (data.error) { console.error('流式响应错误:', data.error); aiLoadingDiv.innerHTML = `
${data.error}
`; reject(data.error); } } }); } catch (e) { console.error('解析流式数据时出错:', e); } }; // 处理请求完成 xhr.onload = function() { if (xhr.status === 200) { // 移除加载提示 aiLoadingDiv.remove(); console.log('流式请求完成'); resolve(actualContent); } else { console.error('AI分析请求失败:', xhr.status, xhr.statusText); aiLoadingDiv.innerHTML = `
请求失败: ${xhr.statusText}
`; reject(`请求失败: ${xhr.statusText}`); } }; // 处理请求错误 xhr.onerror = function(e) { console.error('AI分析请求网络错误:', e); aiLoadingDiv.innerHTML = '
网络错误,请稍后再试
'; reject('网络错误,请稍后再试'); }; // 发送请求 xhr.send(JSON.stringify(requestData)); } else { // 非流式响应处理 fetch('/ai_analysis', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify(requestData) }) .then(response => { if (!response.ok) { throw new Error(`HTTP错误 ${response.status}`); } return response.json(); }) .then(data => { // 移除加载提示 aiLoadingDiv.remove(); if (data.success) { // 显示AI分析结果 aiContentDiv.innerHTML = convertMarkdownToHTML(data.result); // 如果提供了重定向URL,处理重定向 if (data.redirect) { alert('AI分析已完成,即将跳转到详情页面查看'); setTimeout(() => { window.location.href = data.redirect; }, 1000); } resolve(data.result); } else { throw new Error(data.error || '获取AI分析失败'); } }) .catch(error => { console.error('AI分析请求失败:', error); aiLoadingDiv.innerHTML = `
${error.message}
`; reject(error); }); } }); } /** * 保存分析结果到数据库(在请求AI分析前) * @param {Object} data - 分析数据 * @returns {Promise} 返回分析ID(UUID格式)或null */ function saveAnalysisFirst(data) { const resultDiv = document.getElementById('result'); // 创建保存中提示 const savingNotice = document.createElement('div'); savingNotice.className = 'saving-notice'; savingNotice.innerHTML = '正在保存分析结果...'; resultDiv.appendChild(savingNotice); // 构造保存数据 const saveData = { name: data.name || '', gender: data.gender || '', birth_date: data.birth_date || '', birth_time: data.birth_time || '', birth_place: data.birth_place || '', analysis_data: JSON.stringify(data) }; console.log('[调试] 发送保存分析请求:', saveData); // 发送保存请求 return fetch('/save_analysis', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify(saveData) }) .then(response => response.json()) .then(result => { savingNotice.remove(); if (result.success) { console.log(`[调试] 分析结果已保存,ID: ${result.analysis_id}`); // 返回生成的分析ID(UUID格式) return result.analysis_id; } else { console.error('[错误] 保存分析结果失败:', result.error); throw new Error(result.error || '保存分析结果失败'); } }) .catch(error => { savingNotice.remove(); console.error('[错误] 保存分析请求错误:', error); // 显示错误提示 const errorNotice = document.createElement('div'); errorNotice.className = 'error-message'; errorNotice.innerHTML = `${error.message || '保存分析结果失败'}`; resultDiv.appendChild(errorNotice); // 3秒后移除错误提示 setTimeout(() => errorNotice.remove(), 3000); return null; }); } /** * 添加"请求AI分析"按钮 * @param {Object} data - 八字分析数据 * @param {HTMLElement} container - 结果容器元素 */ function addAIAnalysisButton(data, container) { // 创建一个容器,包含选择模型类型和按钮 const aiControlsContainer = document.createElement('div'); aiControlsContainer.className = 'ai-controls-container mt-4 text-center'; // 添加模型选择选项 const modelSelectionDiv = document.createElement('div'); modelSelectionDiv.className = 'model-selection mb-4'; modelSelectionDiv.innerHTML = `

选择分析模型

今日剩余: 0/3
今日剩余: 0/1
`; aiControlsContainer.appendChild(modelSelectionDiv); // 创建按钮 const aiButton = document.createElement('button'); aiButton.className = 'btn btn-primary ai-analysis-button'; aiButton.innerHTML = '获取AI深度分析'; aiControlsContainer.appendChild(aiButton); // 获取并显示模型使用情况 fetch('/api/model_usage') .then(response => response.json()) .then(data => { if (data.success) { // 使用通用的更新UI函数 updateModelSelectionUI(data.usage, aiControlsContainer); } else { console.error('获取模型使用情况失败:', data.error); } }) .catch(error => { console.error('获取模型使用情况请求失败:', error); }); // 添加点击事件处理 aiButton.addEventListener('click', function() { // 获取选择的模型类型 let selectedModelType = 'basic'; // 默认使用基础模型 const modelTypeRadios = document.querySelectorAll('input[name="modelType"]'); modelTypeRadios.forEach(radio => { if (radio.checked) { selectedModelType = radio.value; } }); console.log(`选择的模型类型: ${selectedModelType}`); // 禁用按钮防止重复点击 this.disabled = true; this.innerHTML = ' AI分析中'; // 请求AI分析 // 检查是否有分析ID,如果没有,需要先保存分析结果 if (data.analysis_id) { // 如果已有分析ID,直接使用 console.log(`[调试] 使用已有分析ID进行AI分析: ${data.analysis_id}, 模型类型: ${selectedModelType}`); requestAIAnalysis(data, data.analysis_id, true, null, selectedModelType) .finally(() => { // 移除按钮 aiControlsContainer.remove(); }); } else { // 如果没有分析ID,先保存分析结果,然后使用返回的分析ID console.log('[调试] 保存分析并获取UUID...'); saveAnalysisFirst(data) .then(analysisId => { if (analysisId) { console.log(`[调试] 获取到分析ID: ${analysisId},开始AI分析,模型类型: ${selectedModelType}`); // 保存ID到data对象,以便后续使用 data.analysis_id = analysisId; // 调用AI分析,传递ID和模型类型 return requestAIAnalysis(data, analysisId, true, null, selectedModelType); } else { throw new Error('无法获取分析ID'); } }) .catch(error => { console.error('[错误] AI分析请求失败:', error); // 恢复按钮状态,允许重试 this.disabled = false; this.innerHTML = '重新尝试AI分析'; }) .finally(() => { if (!this.disabled) return; // 如果按钮已恢复,不要移除 // 移除按钮 aiControlsContainer.remove(); }); } }); // 添加到结果区域 const resultDiv = container || document.getElementById('result'); const lastCard = resultDiv.querySelector('.result-card:last-child, .unified-analysis-card'); if (lastCard) { lastCard.parentNode.insertBefore(aiControlsContainer, lastCard.nextSibling); } else { resultDiv.appendChild(aiControlsContainer); } return aiButton; } /** * 更新模型选择UI中的剩余次数显示 * @param {Object} usage - 使用情况数据 * @param {Element} container - 可选容器,如果指定则仅更新此容器内的元素 */ function updateModelSelectionUI(usage, container = document) { // 处理model-option中的usage-count标签 const basicUsageCountElements = container.querySelectorAll('.model-option:has(#basicModel) .usage-count, .model-option:has(#streamBasicModel) .usage-count'); const deepUsageCountElements = container.querySelectorAll('.model-option:has(#deepModel) .usage-count, .model-option:has(#streamDeepModel) .usage-count'); // 更新基础模型剩余次数 if (basicUsageCountElements.length > 0) { const basicRemaining = usage.basic.remaining; basicUsageCountElements.forEach(el => { el.textContent = basicRemaining; // 获取父元素model-option const modelOption = el.closest('.model-option'); if (modelOption) { // 清除现有的警告/危险样式 modelOption.classList.remove('warning', 'danger'); // 添加相应样式 if (basicRemaining === 0) { modelOption.classList.add('danger'); // 禁用单选按钮 const radio = modelOption.querySelector('input[type="radio"]'); if (radio) radio.disabled = true; } else if (basicRemaining === 1) { modelOption.classList.add('warning'); } } }); } // 更新深度模型剩余次数 if (deepUsageCountElements.length > 0) { const deepRemaining = usage.deep.remaining; deepUsageCountElements.forEach(el => { el.textContent = deepRemaining; // 获取父元素model-option const modelOption = el.closest('.model-option'); if (modelOption) { // 清除现有的警告/危险样式 modelOption.classList.remove('warning', 'danger'); // 添加相应样式 if (deepRemaining === 0) { modelOption.classList.add('danger'); // 禁用单选按钮 const radio = modelOption.querySelector('input[type="radio"]'); if (radio) radio.disabled = true; // 如果深度模型不可用,确保选择基础模型 const basicRadio = container.querySelector('#basicModel, #streamBasicModel'); if (basicRadio && !basicRadio.disabled) { basicRadio.checked = true; } } else if (deepRemaining === 1) { modelOption.classList.add('warning'); } } }); } // 更新model-usage-badge const basicBadges = container.querySelectorAll('#basicModelUsage, #streamBasicModelUsage'); const deepBadges = container.querySelectorAll('#deepModelUsage, #streamDeepModelUsage'); // 更新基础模型badge if (basicBadges.length > 0) { const basicRemaining = usage.basic.remaining; basicBadges.forEach(badge => { badge.textContent = `剩余${basicRemaining}次`; // 清除现有样式 badge.classList.remove('warning', 'danger'); // 添加相应样式 if (basicRemaining === 0) { badge.classList.add('danger'); } else if (basicRemaining === 1) { badge.classList.add('warning'); } }); } // 更新深度模型badge if (deepBadges.length > 0) { const deepRemaining = usage.deep.remaining; deepBadges.forEach(badge => { badge.textContent = `剩余${deepRemaining}次`; // 清除现有样式 badge.classList.remove('warning', 'danger'); // 添加相应样式 if (deepRemaining === 0) { badge.classList.add('danger'); } else if (deepRemaining === 1) { badge.classList.add('warning'); } }); } // 更新分析按钮状态 const analysisButtons = container.querySelectorAll('.ai-analysis-button'); if (analysisButtons.length > 0 && usage.basic.remaining === 0 && usage.deep.remaining === 0) { analysisButtons.forEach(btn => { btn.disabled = true; btn.innerHTML = '今日分析次数已用完'; btn.classList.add('disabled'); }); } } /** * 请求AI分析数据并处理流式响应 * @param {string} responseId - 响应ID */ function requestStreamAIAnalysis(responseId) { if (!responseId) { showToast('错误', '无效的响应ID', 'error'); return; } // 检查是否已经有分析按钮,避免重复添加 if (document.querySelector('.ai-analysis-btn')) { console.log('AI分析按钮已存在,不重复添加'); return; } // 创建一个容器,包含模型选择选项和按钮 const aiControlsContainer = document.createElement('div'); aiControlsContainer.className = 'ai-controls-container mt-3 text-center'; // 添加模型选择选项 const modelSelectionDiv = document.createElement('div'); modelSelectionDiv.className = 'model-selection mb-4'; modelSelectionDiv.innerHTML = `

选择分析模型

今日剩余: 0/3
今日剩余: 0/1
`; // 添加AI分析按钮 const analysisBtn = document.createElement('button'); analysisBtn.className = 'btn btn-primary ai-analysis-button'; analysisBtn.innerHTML = '开始AI分析'; // 添加元素到容器 aiControlsContainer.appendChild(modelSelectionDiv); aiControlsContainer.appendChild(analysisBtn); // 将整个容器添加到页面 const resultContainer = document.querySelector('.result-container'); if (!resultContainer) { console.log('未找到.result-container元素,尝试查找并创建替代容器'); const aiTab = document.getElementById('ai'); if (aiTab) { aiTab.classList.add('result-container'); aiTab.appendChild(aiControlsContainer); } else { // 如果连#ai都没找到,尝试使用#result或body const fallbackContainer = document.getElementById('result') || document.body; console.log('使用备用容器:', fallbackContainer.tagName); fallbackContainer.appendChild(aiControlsContainer); } } else { resultContainer.appendChild(aiControlsContainer); } // 分析按钮点击事件 analysisBtn.onclick = function() { // 获取选择的模型类型 let selectedModelType = 'basic'; // 默认使用基础模型 const modelTypeRadios = document.querySelectorAll('input[name="streamModelType"]'); modelTypeRadios.forEach(radio => { if (radio.checked) { selectedModelType = radio.value; } }); console.log(`选择的模型类型: ${selectedModelType}`); // 显示分析中的加载提示 showLoadingModal('AI正在深度分析中...', '这可能需要10-20秒,请耐心等待'); // 创建并显示分析结果容器 const analysisContainer = document.createElement('div'); // 使用正确的requestAIAnalysis函数,调用/ai_analysis接口 // 参数: baziData=null, analysisId=分析ID, useStream=true, progressCallback=null, modelType=选择的模型类型 requestAIAnalysis(null, responseId, true, null, selectedModelType) .then(() => { hideLoadingModal(); console.log('AI分析完成'); }) .catch(error => { hideLoadingModal(); console.error('AI分析请求失败:', error); showToast('错误', error.message || '请求失败', 'error'); }); }; // 获取并显示模型使用情况 fetch('/api/model_usage') .then(response => response.json()) .then(data => { if (data.success) { // 使用通用的更新UI函数 updateModelSelectionUI(data.usage, aiControlsContainer); } else { console.error('获取模型使用情况失败:', data.error); } }) .catch(error => { console.error('获取模型使用情况请求失败:', error); }); return analysisBtn; } /** * 添加AI分析内容样式 */ function addAIAnalysisStyles() { // 添加样式 const styles = ` .ai-list { padding-left: 1.5em; margin-top: 0.6em; margin-bottom: 1em; } .ai-list-item { margin-bottom: 0.7em; position: relative; } .ai-list-item:last-child { margin-bottom: 0; } .markdown-hr { border: 0; height: 1px; background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0)); margin: 1em 0; } .section-divider { border: 0; height: 1px; background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0)); margin: 1.2em 0 0.7em 0; } .section-with-divider { margin-top: 1em; } .h4-small-margin { margin-top: 0.5em; margin-bottom: 0.8em; font-weight: 600; color: #333; } h4.section-title { font-weight: 600; color: #444; margin-top: 1.2em; margin-bottom: 0.8em; } h4.small-margin { margin-top: 0.7em; } h3.section-title { font-weight: 700; color: #333; margin-top: 1.5em; margin-bottom: 1em; } .markdown-content p + .section-with-divider { margin-top: 1.5em; } .markdown-content ul + .section-with-divider, .markdown-content ol + .section-with-divider { margin-top: 1.2em; } .markdown-content .section-with-divider + p { margin-top: 0.6em; } /* 确保第一行标题正确显示 */ .markdown-content > div:first-child { margin-top: 0 !important; } .markdown-content > .section-with-divider:first-child hr.section-divider { display: none; /* 第一个分隔线隐藏,避免页面顶部有多余的间隔 */ } `; // 检查是否已存在样式元素 let styleElement = document.getElementById('ai-analysis-styles'); if (!styleElement) { styleElement = document.createElement('style'); styleElement.id = 'ai-analysis-styles'; document.head.appendChild(styleElement); } styleElement.textContent = styles; } // 初始化时添加样式 document.addEventListener('DOMContentLoaded', addAIAnalysisStyles); function setupAIAnalysisRequest() { const requestAiButton = document.getElementById('requestAiAnalysis'); if (requestAiButton) { // 确保分析区域左对齐 const aiContainer = document.querySelector('#ai'); if (aiContainer) { const centerElements = aiContainer.querySelectorAll('.text-center'); centerElements.forEach(el => { if (!el.querySelector('button')) { // 保留按钮的居中 el.classList.remove('text-center'); el.style.textAlign = 'left'; } }); } // 页面加载时确保流式内容区域左对齐 const streamingContent = document.getElementById('streamingContent'); if (streamingContent) { streamingContent.style.textAlign = 'left'; if (streamingContent.parentElement) { streamingContent.parentElement.style.textAlign = 'left'; } } requestAiButton.addEventListener('click', function() { // ... existing code ... // 自定义流处理函数,将收到的内容实时显示 const customProgressHandler = (chunk) => { if (chunk && streamingContent) { // 累积分析文本 analysisText += chunk; // 处理文本,如果有第一行是标题,删除它 let processedText = analysisText; let lines = processedText.split('\n'); // 优化处理第一行标题的逻辑,确保能正确处理任何级别的标题(包括###) if (lines.length > 1) { const firstLine = lines[0].trim(); // 处理所有级别的Markdown标题和空行 if (firstLine.match(/^#{1,6}\s+/) || firstLine === '') { console.log('删除标题行:', firstLine); processedText = lines.slice(1).join('\n'); } } // 实时更新显示,确保使用统一的markdown转换函数 streamingContent.innerHTML = convertMarkdownToHTML(processedText); // 确保样式一致性 if (typeof addAIAnalysisStyles === 'function') { addAIAnalysisStyles(); } // 确保流式内容是左对齐的 streamingContent.style.textAlign = 'left'; if (streamingContent.parentElement) { streamingContent.parentElement.style.textAlign = 'left'; } // 移除可能导致居中的容器样式 const containers = document.querySelectorAll('.text-center'); containers.forEach(container => { if (container.contains(streamingContent)) { container.classList.remove('text-center'); } }); // 自动滚动到底部 streamingContent.scrollTop = streamingContent.scrollHeight; } }; // ... existing code ... }); } } /** * 初始化工具提示 */ function initializeTooltips() { const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); } /** * 初始化复制按钮 */ function initializeCopyButtons() { document.querySelectorAll('.copy-btn').forEach(button => { button.addEventListener('click', function() { const input = this.previousElementSibling; if (input) { input.select(); document.execCommand('copy'); // 更新按钮文本 const originalText = this.innerHTML; this.innerHTML = ' 已复制'; setTimeout(() => { this.innerHTML = originalText; }, 2000); } }); }); } // ... existing code ... /** * 初始化性别选择器 */ function initializeGenderSelector() { console.log('开始初始化性别选择器'); const genderOptions = document.querySelectorAll('.gender-option'); const genderInput = document.getElementById('gender'); const genderFeedback = document.getElementById('gender-feedback'); if (!genderOptions.length) { console.error('未找到性别选项元素'); return; } if (!genderInput) { console.error('未找到性别输入框元素'); return; } // 隐藏性别验证反馈 if (genderFeedback) { genderFeedback.style.display = 'none'; } console.log(`找到 ${genderOptions.length} 个性别选项`); genderOptions.forEach(option => { option.addEventListener('click', function(e) { e.preventDefault(); const selectedGender = this.dataset.gender; console.log('点击性别选项:', selectedGender); // 移除其他选项的选中状态 genderOptions.forEach(opt => { opt.classList.remove('selected', 'active'); }); // 添加当前选项的选中状态 this.classList.add('selected', 'active'); // 更新隐藏输入框的值 genderInput.value = selectedGender; console.log('更新性别值为:', genderInput.value); // 隐藏验证反馈 if (genderFeedback) { genderFeedback.style.opacity = '0'; setTimeout(() => { genderFeedback.style.display = 'none'; genderFeedback.style.opacity = '1'; }, 300); } // 移除动画效果 genderOptions.forEach(opt => { opt.style.animation = ''; }); // 触发change事件以触发表单验证 const event = new Event('change', { bubbles: true, cancelable: true, }); genderInput.dispatchEvent(event); }); }); } // 在DOMContentLoaded事件中调用初始化函数 document.addEventListener('DOMContentLoaded', function() { console.log('DOM加载完成,开始初始化表单'); initializeGenderSelector(); initializeDateSelectors(); // 监听表单提交事件 const fortuneForm = document.getElementById('fortuneForm'); if (fortuneForm) { fortuneForm.addEventListener('submit', function(e) { e.preventDefault(); handleFortuneFormSubmit(this); }); } }); // ... existing code ...