This commit is contained in:
HuangHai
2026-01-21 08:41:47 +08:00
parent 306a3a4127
commit e51dc18d06
40 changed files with 1309 additions and 965 deletions

View File

@@ -12,6 +12,7 @@ createApp({
window.addEventListener('resize', () => {
isMobile.value = window.innerWidth <= 768;
if (chartInstance) chartInstance.resize();
if (trendChartInstance) trendChartInstance.resize();
});
// ==========================================
@@ -35,7 +36,9 @@ createApp({
const priceTableRows = ref([]);
const hourlyPricesByOperator = ref({});
let chartInstance = null;
let trendChartInstance = null;
const chartType = ref('line');
const trendDays = ref(7);
// ECharts Initialization
const initChart = () => {
@@ -49,6 +52,17 @@ createApp({
}
};
const initTrendChart = () => {
const dom = document.getElementById("trendChart");
if (dom && !trendChartInstance) {
if (typeof echarts === 'undefined') {
console.error("ECharts not loaded");
return;
}
trendChartInstance = echarts.init(dom);
}
};
const renderChart = () => {
if (!chartInstance) return;
@@ -152,6 +166,65 @@ createApp({
}
};
const loadTrendData = async () => {
try {
const res = await axios.get(apiBase.value + "/api/operators/price-trends?days=" + trendDays.value);
if (res && res.data) {
renderTrendChart(res.data);
}
} catch (e) {
console.error("Failed to load trend data:", e);
}
};
const renderTrendChart = (data) => {
if (!trendChartInstance) initTrendChart();
if (!trendChartInstance) return;
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: "axis",
backgroundColor: 'rgba(30, 41, 59, 0.9)',
borderColor: '#334155',
textStyle: { color: '#f1f5f9' }
},
legend: {
data: data.series.map(s => s.name),
textStyle: { color: "#94a3b8" },
top: 10
},
grid: { left: 50, right: 30, top: 60, bottom: 40 },
xAxis: {
type: "category",
data: data.dates.map(d => d.split('-').slice(1).join('/')), // Show MM/DD
axisLine: { lineStyle: { color: "#475569" } },
axisLabel: { color: "#94a3b8" }
},
yAxis: {
type: "value",
name: "元/度",
nameTextStyle: { color: "#94a3b8" },
axisLine: { lineStyle: { color: "#475569" } },
axisLabel: { color: "#94a3b8" },
splitLine: { lineStyle: { color: "#334155", type: 'dashed' } },
min: (value) => (value.min * 0.95).toFixed(2),
max: (value) => (value.max * 1.05).toFixed(2)
},
series: data.series.map(s => ({
name: s.name,
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 6,
data: s.data,
emphasis: { focus: 'series' }
}))
};
trendChartInstance.setOption(option, true);
};
const exportAllPrices = async () => {
try{
exporting.value = true;
@@ -244,48 +317,102 @@ createApp({
return '#f1f5f9'; // Slate-100
};
// 内置简易 Markdown 解析器
// Configure Marked
if (typeof marked !== 'undefined') {
marked.use({
gfm: true,
breaks: true
});
}
// 降级用的简易 Markdown 解析器
const simpleMarkdown = (text) => {
if (!text) return '';
let lines = text.split('\n');
let html = '';
let inList = false;
const parseInline = (str) => {
return str
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/`(.*?)`/g, '<code style="background:#f1f5f9; padding:2px 4px; border-radius:4px; color:#334155;">$1</code>');
.replace(/`(.*?)`/g, '<code style="background:rgba(148, 163, 184, 0.1); padding:2px 4px; border-radius:4px;">$1</code>');
};
for (let line of lines) {
let trimmed = line.trim();
if (!trimmed) continue;
if (!trimmed) {
if (inList) { html += '</ul>'; inList = false; }
html += '<br>';
continue;
}
if (trimmed.startsWith('### ')) {
if (inList) { html += '</ul>'; inList = false; }
html += `<h3>${parseInline(trimmed.substring(4))}</h3>`;
}
else if (trimmed.startsWith('- ') || /^\d+\./.test(trimmed)) {
} else if (trimmed.startsWith('- ') || /^\d+\./.test(trimmed)) {
if (!inList) { html += '<ul>'; inList = true; }
let content = trimmed.replace(/^(- |\d+\. )/, '');
html += `<li>${parseInline(content)}</li>`;
}
else {
} else {
if (inList) { html += '</ul>'; inList = false; }
html += `<p style="margin-bottom:8px;">${parseInline(trimmed)}</p>`;
html += `<p>${parseInline(trimmed)}</p>`;
}
}
if (inList) html += '</ul>';
return html;
};
// 增强的 Markdown & LaTeX 解析器
const renderMarkdownAndLatex = (text) => {
if (!text) return '';
try {
// 1. 处理 LaTeX (简单替换,先处理 $$ 再处理 $)
let processedText = text;
// 处理块级 LaTeX: $$ ... $$
processedText = processedText.replace(/\$\$\s*([\s\S]*?)\s*\$\$/g, (match, formula) => {
try {
if (typeof katex !== 'undefined') {
return '<div class="katex-block">' + katex.renderToString(formula, { displayMode: true, throwOnError: false }) + '</div>';
}
return match;
} catch (e) {
return match;
}
});
// 处理行内 LaTeX: $ ... $
processedText = processedText.replace(/\$([^\$\n]+?)\$/g, (match, formula) => {
try {
if (typeof katex !== 'undefined') {
return katex.renderToString(formula, { displayMode: false, throwOnError: false });
}
return match;
} catch (e) {
return match;
}
});
// 2. 使用 marked 解析 Markdown
if (typeof marked !== 'undefined') {
return marked.parse(processedText);
} else {
// 降级使用之前的 simpleMarkdown
return simpleMarkdown(processedText);
}
} catch (e) {
console.error('Markdown/LaTeX rendering error:', e);
return text;
}
};
const renderedAiText = computed(() => {
return simpleMarkdown(aiText.value);
return renderMarkdownAndLatex(aiText.value);
});
onMounted(() => {
initChart();
initTrendChart();
loadAllOperatorsPrices();
loadTrendData();
});
return {
@@ -299,9 +426,11 @@ createApp({
priceTableRows,
hourlyPricesByOperator,
chartType,
trendDays,
// Actions
loadAllOperatorsPrices,
loadTrendData,
exportAllPrices,
exportAiReport,
startAiAnalysis,