You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

670 lines
24 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>云南省市州教育数据AI对比分析器</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.button-container {
text-align: center;
margin-bottom: 30px;
}
#generateHtmlBtn, #generateWordBtn, #generatePptBtn {
background-color: #007bff;
color: white;
border: none;
padding: 12px 20px;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
margin: 0 5px;
}
#generateHtmlBtn:hover, #generateWordBtn:hover, #generatePptBtn:hover {
background-color: #0056b3;
}
#generateHtmlBtn:disabled, #generateWordBtn:disabled, #generatePptBtn:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
#generateHtmlBtn {
background-color: #28a745;
}
#generateHtmlBtn:hover {
background-color: #218838;
}
#generateWordBtn {
background-color: #007bff;
}
#generatePptBtn {
background-color: #fd7e14;
}
#generatePptBtn:hover {
background-color: #e8650e;
}
.status {
text-align: center;
margin-bottom: 20px;
font-weight: bold;
}
.status.connecting {
color: #ffc107;
}
.status.connected {
color: #28a745;
}
.status.error {
color: #dc3545;
}
.status.completed {
color: #17a2b8;
}
.data-area {
border: 1px solid #ddd;
border-radius: 5px;
padding: 15px;
min-height: 300px;
max-height: 400px;
overflow-y: auto;
background-color: #f8f9fa;
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.6;
white-space: pre-line;
word-wrap: break-word;
overflow-wrap: break-word;
}
.download-section {
margin-top: 20px;
padding: 15px;
background-color: #e9ecef;
border-radius: 5px;
}
.download-links {
margin-top: 10px;
}
.download-link {
display: inline-block;
margin: 5px 10px 5px 0;
padding: 8px 15px;
background-color: #28a745;
color: white;
text-decoration: none;
border-radius: 4px;
transition: background-color 0.3s;
}
.download-link:hover {
background-color: #218838;
text-decoration: none;
color: white;
}
.clear-btn {
background-color: #6c757d;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
.clear-btn:hover {
background-color: #545b62;
}
.city-selection {
margin-bottom: 30px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 5px;
border: 1px solid #dee2e6;
}
.city-selection h3 {
margin-top: 0;
color: #495057;
text-align: center;
}
.city-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
margin-top: 15px;
}
.city-item {
display: flex;
align-items: center;
padding: 8px;
background-color: white;
border: 1px solid #ced4da;
border-radius: 4px;
transition: background-color 0.2s;
}
.city-item:hover {
background-color: #e9ecef;
}
.city-item input[type="checkbox"] {
margin-right: 8px;
}
.city-item label {
cursor: pointer;
flex: 1;
margin: 0;
}
.selection-info {
text-align: center;
margin-top: 15px;
font-size: 14px;
color: #6c757d;
}
.selection-info.error {
color: #dc3545;
}
.selection-info.success {
color: #28a745;
}
</style>
</head>
<body>
<div class="container">
<h1>云南省市州教育数据AI对比分析器</h1>
<div class="city-selection">
<h3>请选择两个市州进行教育数据对比</h3>
<div id="cityGrid" class="city-grid">
<!-- 市州列表将通过JavaScript动态加载 -->
</div>
<div id="selectionInfo" class="selection-info">请选择恰好两个市州</div>
</div>
<div class="button-container">
<button id="generateHtmlBtn" onclick="startGeneration('html')">生成HTML</button>
<button id="generateWordBtn" onclick="startGeneration('word')">生成WORD</button>
<button id="generatePptBtn" onclick="startGeneration('ppt')">生成PPT</button>
</div>
<div id="status" class="status">准备就绪</div>
<div class="data-area" id="dataArea">等待数据...</div>
<button class="clear-btn" onclick="clearData()">清空数据</button>
<div class="download-section">
<h3>下载文件:</h3>
<div id="downloadLinks" class="download-links">
<span style="color: #6c757d;">暂无可下载文件</span>
</div>
</div>
</div>
<script>
let eventSource = null;
let downloadUrls = [];
let textBuffer = ''; // 用于缓存文本,优化换行显示
let selectedCities = []; // 存储选中的市州
let cityData = []; // 存储市州数据
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
loadCityData();
});
// 加载市州数据
function loadCityData() {
fetch('http://10.10.21.20:9200/dsBase/ai/getYunNanCity')
.then(response => response.json())
.then(data => {
if (data.code === 0 && data.data) {
cityData = data.data;
renderCityGrid();
} else {
console.error('加载市州数据失败:', data.msg);
document.getElementById('selectionInfo').textContent = '加载市州数据失败,请刷新页面重试';
document.getElementById('selectionInfo').className = 'selection-info error';
}
})
.catch(error => {
console.error('请求市州数据失败:', error);
document.getElementById('selectionInfo').textContent = '网络错误,请检查网络连接后刷新页面';
document.getElementById('selectionInfo').className = 'selection-info error';
});
}
// 渲染市州选择网格
function renderCityGrid() {
const cityGrid = document.getElementById('cityGrid');
cityGrid.innerHTML = '';
cityData.forEach(city => {
const cityItem = document.createElement('div');
cityItem.className = 'city-item';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = 'city_' + city.id;
checkbox.value = city.area_name;
checkbox.addEventListener('change', handleCitySelection);
const label = document.createElement('label');
label.htmlFor = 'city_' + city.id;
label.textContent = city.area_name;
cityItem.appendChild(checkbox);
cityItem.appendChild(label);
cityGrid.appendChild(cityItem);
});
}
// 处理市州选择
function handleCitySelection(event) {
const checkbox = event.target;
const cityName = checkbox.value;
if (checkbox.checked) {
if (selectedCities.length >= 2) {
// 如果已经选择了2个取消当前选择
checkbox.checked = false;
updateSelectionInfo();
return;
}
selectedCities.push(cityName);
} else {
const index = selectedCities.indexOf(cityName);
if (index > -1) {
selectedCities.splice(index, 1);
}
}
updateSelectionInfo();
updateButtonStates();
}
// 更新选择信息显示
function updateSelectionInfo() {
const selectionInfo = document.getElementById('selectionInfo');
if (selectedCities.length === 0) {
selectionInfo.textContent = '请选择恰好两个市州';
selectionInfo.className = 'selection-info';
} else if (selectedCities.length === 1) {
selectionInfo.textContent = `已选择:${selectedCities[0]},请再选择一个市州`;
selectionInfo.className = 'selection-info';
} else if (selectedCities.length === 2) {
selectionInfo.textContent = `已选择:${selectedCities[0]}${selectedCities[1]}`;
selectionInfo.className = 'selection-info success';
}
}
// 更新按钮状态
function updateButtonStates() {
const generateHtmlBtn = document.getElementById('generateHtmlBtn');
const generateWordBtn = document.getElementById('generateWordBtn');
const generatePptBtn = document.getElementById('generatePptBtn');
const canGenerate = selectedCities.length === 2;
generateHtmlBtn.disabled = !canGenerate;
generateWordBtn.disabled = !canGenerate;
generatePptBtn.disabled = !canGenerate;
}
function startGeneration(type) {
// 验证是否选择了恰好两个市州
if (selectedCities.length !== 2) {
alert('请选择恰好两个市州进行对比分析!');
return;
}
const generateHtmlBtn = document.getElementById('generateHtmlBtn');
const generateWordBtn = document.getElementById('generateWordBtn');
const generatePptBtn = document.getElementById('generatePptBtn');
const statusDiv = document.getElementById('status');
const dataArea = document.getElementById('dataArea');
// 禁用所有按钮
generateHtmlBtn.disabled = true;
generateWordBtn.disabled = true;
generatePptBtn.disabled = true;
// 更新当前按钮文本
if (type === 'html') {
generateHtmlBtn.textContent = '生成HTML中...';
} else if (type === 'word') {
generateWordBtn.textContent = '生成WORD中...';
} else if (type === 'ppt') {
generatePptBtn.textContent = '生成PPT中...';
}
// 清空之前的数据
dataArea.textContent = '';
textBuffer = '';
downloadUrls = [];
updateDownloadLinks();
// 更新状态
statusDiv.textContent = '正在连接...';
statusDiv.className = 'status connecting';
// 根据类型选择不同的API接口使用选中的市州
const shiZhouA = encodeURIComponent(selectedCities[0]);
const shiZhouB = encodeURIComponent(selectedCities[1]);
let apiUrl;
if (type === 'html') {
apiUrl = `/dsBase/ai/compareShiZhouHtml?shiZhouA=${shiZhouA}&shiZhouB=${shiZhouB}`;
} else if (type === 'word') {
apiUrl = `/dsBase/ai/compareShiZhouWord?shiZhouA=${shiZhouA}&shiZhouB=${shiZhouB}`;
} else if (type === 'ppt') {
apiUrl = `/dsBase/ai/compareShiZhouPpt?shiZhouA=${shiZhouA}&shiZhouB=${shiZhouB}`;
}
// 创建SSE连接
eventSource = new EventSource(apiUrl);
eventSource.onopen = function(event) {
statusDiv.textContent = '连接成功,等待数据...';
statusDiv.className = 'status connected';
};
eventSource.onmessage = function(event) {
let data = event.data;
// 去掉 "data: " 前缀
if (data.startsWith('data: ')) {
data = data.substring(6);
}
// 跳过空数据或只包含空白字符的数据
if (!data.trim()) {
return;
}
// 检查是否是结束标记
if (data.trim() === '[DONE]') {
statusDiv.textContent = '数据生成完成';
statusDiv.className = 'status completed';
// 重新启用所有按钮
enableAllButtons();
// 关闭连接
eventSource.close();
eventSource = null;
return;
}
// 将数据添加到缓冲区
textBuffer += data;
// 智能换行处理:检查是否应该换行
processTextBuffer();
// 自动滚动到底部
dataArea.scrollTop = dataArea.scrollHeight;
// 检查是否包含下载链接
checkForDownloadLinks(data);
};
eventSource.onerror = function(event) {
console.error('SSE连接错误:', event);
statusDiv.textContent = '连接错误或已断开';
statusDiv.className = 'status error';
// 重新启用所有按钮
enableAllButtons();
// 关闭连接
if (eventSource) {
eventSource.close();
eventSource = null;
}
};
// 监听自定义事件(如果服务器发送特定事件类型)
eventSource.addEventListener('complete', function(event) {
statusDiv.textContent = '数据生成完成';
statusDiv.className = 'status completed';
// 重新启用所有按钮
enableAllButtons();
// 关闭连接
eventSource.close();
eventSource = null;
});
}
function enableAllButtons() {
const generateHtmlBtn = document.getElementById('generateHtmlBtn');
const generateWordBtn = document.getElementById('generateWordBtn');
const generatePptBtn = document.getElementById('generatePptBtn');
// 只有选择了两个市州才能启用按钮
const canGenerate = selectedCities.length === 2;
generateHtmlBtn.disabled = !canGenerate;
generateWordBtn.disabled = !canGenerate;
generatePptBtn.disabled = !canGenerate;
generateHtmlBtn.textContent = '生成HTML';
generateWordBtn.textContent = '生成WORD';
generatePptBtn.textContent = '生成PPT';
}
function processTextBuffer() {
const dataArea = document.getElementById('dataArea');
// 首先处理明确的换行符
if (textBuffer.includes('\n')) {
const lines = textBuffer.split('\n');
const completedLines = lines.slice(0, -1);
const remainingText = lines[lines.length - 1];
completedLines.forEach(line => {
if (line.trim()) {
dataArea.innerHTML += line + '<br>';
}
// 空白行不输出任何内容
});
textBuffer = remainingText;
}
// 简化表格处理:只处理完整的表格行
if (textBuffer.includes('|')) {
// 检测并处理完整的表格行(至少包含两个|符号)
const tableRowRegex = /\|[^|]*\|[^|]*\|/;
if (tableRowRegex.test(textBuffer)) {
// 查找表格行的起始位置
const match = textBuffer.match(tableRowRegex);
if (match) {
const beforeTable = textBuffer.substring(0, match.index);
const tableRow = match[0];
const afterTable = textBuffer.substring(match.index + match[0].length);
// 输出表格前的内容
if (beforeTable.trim()) {
dataArea.innerHTML += beforeTable.trim() + '<br>';
}
// 输出表格行
const cleanTableRow = tableRow.replace(/\|/g, ' | ').replace(/\s+/g, ' ').trim();
dataArea.innerHTML += cleanTableRow + '<br>';
// 更新缓冲区
textBuffer = afterTable;
}
}
}
// 处理标题(只在标题前添加换行)
if (textBuffer.includes('#')) {
textBuffer = textBuffer.replace(/(#{1,6}\s*[^#\n]+)/g, '<br>$1');
}
// 处理列表项(简化处理)
if (textBuffer.includes('- ')) {
textBuffer = textBuffer.replace(/([^\n])(-\s+)/g, '$1<br>$2');
}
// 如果缓冲区有内容且包含<br>标签,立即输出
if (textBuffer.includes('<br>')) {
const parts = textBuffer.split('<br>');
for (let i = 0; i < parts.length - 1; i++) {
if (parts[i].trim()) {
dataArea.innerHTML += parts[i].trim() + '<br>';
}
// 空白行不输出任何内容
}
textBuffer = parts[parts.length - 1];
}
// 处理过长的普通文本
if (textBuffer.length > 200) {
let splitIndex = textBuffer.lastIndexOf(' ', 150);
if (splitIndex === -1) {
splitIndex = textBuffer.lastIndexOf('', 150);
}
if (splitIndex === -1) {
splitIndex = textBuffer.lastIndexOf('、', 150);
}
if (splitIndex === -1) {
splitIndex = 150;
}
const textToShow = textBuffer.substring(0, splitIndex + 1);
if (textToShow.trim()) {
dataArea.innerHTML += textToShow + '<br>';
}
textBuffer = textBuffer.substring(splitIndex + 1);
}
}
function checkForDownloadLinks(data) {
// 使用正则表达式匹配URL模式
const urlPattern = /https?:\/\/[^\s]+\.(docx|pdf|xlsx|txt|zip|rar|html|htm)/gi;
const matches = data.match(urlPattern);
if (matches) {
matches.forEach(url => {
if (!downloadUrls.includes(url)) {
downloadUrls.push(url);
}
});
updateDownloadLinks();
}
}
function updateDownloadLinks() {
const downloadLinksDiv = document.getElementById('downloadLinks');
if (downloadUrls.length === 0) {
downloadLinksDiv.innerHTML = '<span style="color: #6c757d;">暂无可下载文件</span>';
return;
}
let linksHtml = '';
downloadUrls.forEach((url, index) => {
const fileName = url.split('/').pop() || `文件${index + 1}`;
// 检查是否为HTML文件
if (fileName.toLowerCase().endsWith('.html') || fileName.toLowerCase().endsWith('.htm')) {
// HTML文件以预览方式打开不下载
linksHtml += `<a href="${url}" class="download-link" target="_blank">${fileName} (预览)</a>`;
} else {
// 其他文件类型正常下载
linksHtml += `<a href="${url}" class="download-link" target="_blank" download>${fileName}</a>`;
}
});
downloadLinksDiv.innerHTML = linksHtml;
}
function clearData() {
const dataArea = document.getElementById('dataArea');
dataArea.innerHTML = '等待数据...';
textBuffer = ''; // 清空文本缓冲区
downloadUrls = [];
updateDownloadLinks();
// 重置市州选择
selectedCities = [];
const checkboxes = document.querySelectorAll('#cityGrid input[type="checkbox"]');
checkboxes.forEach(checkbox => {
checkbox.checked = false;
});
updateSelectionInfo();
updateButtonStates();
}
// 页面卸载时关闭SSE连接
window.addEventListener('beforeunload', function() {
if (eventSource) {
eventSource.close();
}
});
// 停止生成功能(可选)
function stopGeneration() {
if (eventSource) {
eventSource.close();
eventSource = null;
const statusDiv = document.getElementById('status');
enableAllButtons();
statusDiv.textContent = '已停止';
statusDiv.className = 'status error';
}
}
</script>
</body>
</html>