'commit'
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user