const {createApp,onMounted,ref} = Vue createApp({ setup(){ const apiBase = ref(window.location.origin || "http://localhost:8000") const stationHash = ref("") const aiText = ref("") const placeholder = ref("在左侧列表中选择驿来特场站,点击“查看价差”加载24小时分时电价与竞品对比,再点击“AI价差分析”获取模型给出的策略建议。") const loading = ref(false) const aiLoading = ref(false) const stations = ref([]) const searchKeyword = ref("") const selectedStation = ref(null) const priceTableColumns = ref([]) const priceTableRows = ref([]) let chartInstance = null let es = null const initChart = () => { const dom = document.getElementById("chart") if (dom && !chartInstance) { chartInstance = echarts.init(dom) } } const loadStations = async () => { try{ const url = apiBase.value + "/api/ylt/stations" const params = searchKeyword.value ? {params:{q:searchKeyword.value}} : {} const res = await axios.get(url,params) stations.value = Array.isArray(res.data) ? res.data : [] if (!selectedStation.value && stations.value.length > 0) { selectStation(stations.value[0]) } }catch(e){ console.error(e) } } const buildPriceTable = data => { const cols = [] cols.push("时段") cols.push("驿来特") data.competitors.forEach(c => { cols.push(c.operator) }) priceTableColumns.value = cols const rows = [] for (let i = 0; i < data.hours.length; i++) { const row = [] row.push(data.hours[i] + ":00") row.push(data.ylt.series[i]) data.competitors.forEach(c => { row.push(c.series[i]) }) rows.push(row) } priceTableRows.value = rows } const loadComparison = async () => { if (!stationHash.value) return loading.value = true try{ const url = apiBase.value + "/api/ylt/pricing/comparison/" + stationHash.value const res = await axios.get(url) const data = res.data const hours = data.hours.map(h => h + ":00") const series = [] series.push({ name:"驿来特", type:"line", smooth:true, data:data.ylt.series }) data.competitors.forEach(c=>{ series.push({ name:c.operator, type:"line", smooth:true, data:c.series }) }) buildPriceTable(data) const option = { tooltip:{trigger:"axis"}, legend:{data:series.map(s=>s.name),textStyle:{color:"#e5e7eb"}}, xAxis:{type:"category",data:hours,axisLine:{lineStyle:{color:"#4b5563"}},axisLabel:{color:"#9ca3af"}}, yAxis:{type:"value",name:"元/度",axisLine:{lineStyle:{color:"#4b5563"}},axisLabel:{color:"#9ca3af"},splitLine:{lineStyle:{color:"#111827"}}}, grid:{left:40,right:20,top:30,bottom:30}, series:series } initChart() chartInstance.setOption(option) }finally{ loading.value = false } } const selectStation = s => { selectedStation.value = s stationHash.value = s.station_hash loadComparison() } const startAiStream = () => { if (!selectedStation.value || !stationHash.value) return if (es) { es.close() es = null } aiText.value = "" aiLoading.value = true const url = apiBase.value + "/api/ylt/pricing/comparison/" + stationHash.value + "/sse" es = new EventSource(url) es.onmessage = ev => { aiText.value += ev.data } es.onerror = () => { aiLoading.value = false if (es) { es.close() es = null } } } onMounted(()=>{ initChart() loadStations() }) const formatCell = v => { if (v === null || v === undefined || v === "") return "-" if (typeof v === "number") { if (Number.isNaN(v)) return "-" return v.toFixed(2) } return v } return {apiBase,stationHash,aiText,placeholder,loading,aiLoading,stations,searchKeyword,selectedStation,priceTableColumns,priceTableRows,loadStations,loadComparison,selectStation,startAiStream,formatCell} } }).mount("#app")