¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="market-analysis-container"> |
| | | |
| | | <!-- æ°æ®æ¦è§å¡ç --> |
| | | <el-row :gutter="20" class="data-overview"> |
| | | <el-col :span="6"> |
| | | <el-card class="overview-card" shadow="hover"> |
| | | <div class="card-content"> |
| | | <div class="card-icon price-icon"> |
| | | <el-icon><TrendCharts /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-title">å¹³åç
¤ä»·</div> |
| | | <div class="card-value">Â¥{{ marketData.avgPrice.toFixed(2) }}</div> |
| | | <div class="card-change" :class="marketData.priceChange >= 0 ? 'positive' : 'negative'"> |
| | | {{ marketData.priceChange >= 0 ? '+' : '' }}{{ marketData.priceChange.toFixed(2) }}% |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="6"> |
| | | <el-card class="overview-card" shadow="hover"> |
| | | <div class="card-content"> |
| | | <div class="card-icon volume-icon"> |
| | | <el-icon><DataLine /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-title">交æé</div> |
| | | <div class="card-value">{{ marketData.totalVolume }}ä¸å¨</div> |
| | | <div class="card-change" :class="marketData.volumeChange >= 0 ? 'positive' : 'negative'"> |
| | | {{ marketData.volumeChange >= 0 ? '+' : '' }}{{ marketData.volumeChange.toFixed(2) }}% |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="6"> |
| | | <el-card class="overview-card" shadow="hover"> |
| | | <div class="card-content"> |
| | | <div class="card-icon customer-icon"> |
| | | <el-icon><User /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-title">æ´»è·å®¢æ·</div> |
| | | <div class="card-value">{{ marketData.activeCustomers }}å®¶</div> |
| | | <div class="card-change" :class="marketData.customerChange >= 0 ? 'positive' : 'negative'"> |
| | | {{ marketData.customerChange >= 0 ? '+' : '' }}{{ marketData.customerChange.toFixed(2) }}% |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="6"> |
| | | <el-card class="overview-card" shadow="hover"> |
| | | <div class="card-content"> |
| | | <div class="card-icon trend-icon"> |
| | | <el-icon><TrendCharts /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-title">å¸åºè¶å¿</div> |
| | | <div class="card-value">{{ marketData.marketTrend }}</div> |
| | | <div class="card-change" :class="marketData.trendScore >= 0 ? 'positive' : 'negative'"> |
| | | ä¿¡å¿ææ°: {{ marketData.trendScore }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 主è¦åæåºå --> |
| | | <el-row :gutter="20" class="main-analysis"> |
| | | <!-- ä»·æ ¼è¶å¿åæ --> |
| | | <el-col :span="16"> |
| | | <el-card class="analysis-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ç
¤ç§ä»·æ ¼è¶å¿åæ</span> |
| | | <div class="header-controls"> |
| | | <el-select v-model="selectedCoalType" placeholder="éæ©ç
¤ç§" size="small" style="width: 120px"> |
| | | <el-option label="æ··ç
¤" value="mixed" /> |
| | | <el-option label="ç²¾ç
¤" value="refined" /> |
| | | <el-option label="å¨åç
¤" value="power" /> |
| | | <el-option label="ç¦ç
¤" value="coking" /> |
| | | </el-select> |
| | | <el-select v-model="selectedRegion" placeholder="éæ©äº§å°" size="small" style="width: 120px"> |
| | | <el-option label="山西" value="shanxi" /> |
| | | <el-option label="å
èå¤" value="neimenggu" /> |
| | | <el-option label="é西" value="shaanxi" /> |
| | | <el-option label="æ°ç" value="xinjiang" /> |
| | | </el-select> |
| | | <el-select v-model="selectedPeriod" placeholder="æ¶é´å¨æ" size="small" style="width: 100px"> |
| | | <el-option label="æ¥" value="day" /> |
| | | <el-option label="å¨" value="week" /> |
| | | <el-option label="æ" value="month" /> |
| | | <el-option label="å£" value="quarter" /> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="chart-container"> |
| | | <div ref="priceChartRef" class="chart" style="height: 400px;"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- 客æ·è¡ä¸ºåæ --> |
| | | <el-col :span="8"> |
| | | <el-card class="analysis-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>客æ·è¡ä¸ºç»å</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="customer-analysis"> |
| | | <div class="customer-type-distribution"> |
| | | <h4>客æ·ç±»ååå¸</h4> |
| | | <div ref="customerChartRef" class="chart" style="height: 200px;"></div> |
| | | </div> |
| | | |
| | | <div class="purchase-preference"> |
| | | <h4>éè´å好åæ</h4> |
| | | <div class="preference-item" v-for="item in customerPreferences" :key="item.type"> |
| | | <div class="preference-label">{{ item.type }}</div> |
| | | <div class="preference-bar"> |
| | | <div class="bar-fill" :style="{ width: item.percentage + '%' }"></div> |
| | | </div> |
| | | <div class="preference-value">{{ item.percentage }}%</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 详ç»åæåºå --> |
| | | <el-row :gutter="20" class="detail-analysis"> |
| | | <!-- åºåä»·æ ¼å¯¹æ¯ --> |
| | | <el-col :span="12"> |
| | | <el-card class="analysis-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åºåä»·æ ¼å¯¹æ¯</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="region-comparison"> |
| | | <div ref="regionChartRef" class="chart" style="height: 300px;"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- 客æ·éè´å¨æåæ --> |
| | | <el-col :span="12"> |
| | | <el-card class="analysis-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>客æ·éè´å¨æåæ</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="purchase-cycle"> |
| | | <div ref="cycleChartRef" class="chart" style="height: 300px;"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æºè½æ¨èåºå --> |
| | | <el-row :gutter="20" class="smart-recommendations"> |
| | | <el-col :span="24"> |
| | | <el-card class="analysis-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æºè½è¥éæ¨è</span> |
| | | <el-tag type="warning" size="small">AIç®æ³é©±å¨</el-tag> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="recommendations-content"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <div class="recommendation-section"> |
| | | <h4>个æ§åå®ä»·å»ºè®®</h4> |
| | | <div class="pricing-suggestions"> |
| | | <div class="suggestion-item" v-for="suggestion in pricingSuggestions" :key="suggestion.id"> |
| | | <div class="suggestion-header"> |
| | | <span class="customer-name">{{ suggestion.customerName }}</span> |
| | | <el-tag :type="suggestion.priority" size="small">{{ suggestion.priorityText }}</el-tag> |
| | | </div> |
| | | <div class="suggestion-content"> |
| | | <p>å»ºè®®ä»·æ ¼ï¼Â¥{{ suggestion.suggestedPrice }}/å¨</p> |
| | | <p>议价空é´ï¼{{ suggestion.negotiationSpace }}%</p> |
| | | <p>æ¨åæ¶æºï¼{{ suggestion.timing }}</p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <div class="recommendation-section"> |
| | | <h4>çéç
¤åæ¨è</h4> |
| | | <div class="hot-coal-types"> |
| | | <div class="coal-type-item" v-for="coal in hotCoalTypes" :key="coal.id"> |
| | | <div class="coal-info"> |
| | | <div class="coal-name">{{ coal.name }}</div> |
| | | <div class="coal-spec">{{ coal.specification }}</div> |
| | | </div> |
| | | <div class="coal-metrics"> |
| | | <div class="metric"> |
| | | <span class="label">çåº¦ææ°ï¼</span> |
| | | <span class="value">{{ coal.heatIndex }}</span> |
| | | </div> |
| | | <div class="metric"> |
| | | <span class="label">åºåç¶æï¼</span> |
| | | <el-tag :type="coal.stockStatus === 'å
è¶³' ? 'success' : 'warning'" size="small"> |
| | | {{ coal.stockStatus }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <div class="recommendation-section"> |
| | | <h4>客æ·ç²æ§æå</h4> |
| | | <div class="loyalty-improvements"> |
| | | <div class="improvement-item" v-for="improvement in loyaltyImprovements" :key="improvement.id"> |
| | | <div class="improvement-header"> |
| | | <span class="strategy-name">{{ improvement.strategyName }}</span> |
| | | <span class="success-rate">æåç: {{ improvement.successRate }}%</span> |
| | | </div> |
| | | <div class="improvement-content"> |
| | | <p>{{ improvement.description }}</p> |
| | | <div class="action-buttons"> |
| | | <el-button type="primary" size="small">æ§è¡çç¥</el-button> |
| | | <el-button size="small">æ¥ç详æ
</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { Refresh, TrendCharts, DataLine, User } from '@element-plus/icons-vue' |
| | | import * as echarts from 'echarts' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const refreshing = ref(false) |
| | | const lastUpdateTime = ref('') |
| | | const selectedCoalType = ref('mixed') |
| | | const selectedRegion = ref('shanxi') |
| | | const selectedPeriod = ref('month') |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const priceChartRef = ref(null) |
| | | const customerChartRef = ref(null) |
| | | const regionChartRef = ref(null) |
| | | const cycleChartRef = ref(null) |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | let priceChart = null |
| | | let customerChart = null |
| | | let regionChart = null |
| | | let cycleChart = null |
| | | |
| | | // å¸åºæ°æ® |
| | | const marketData = reactive({ |
| | | avgPrice: 1250.50, |
| | | priceChange: 2.35, |
| | | totalVolume: 1250.8, |
| | | volumeChange: -1.25, |
| | | activeCustomers: 156, |
| | | customerChange: 3.45, |
| | | marketTrend: 'ç¨³ä¸æå', |
| | | trendScore: 8.5 |
| | | }) |
| | | |
| | | // 客æ·åå¥½æ°æ® |
| | | const customerPreferences = ref([ |
| | | { type: 'ç¦åå', percentage: 35 }, |
| | | { type: 'çµå', percentage: 28 }, |
| | | { type: 'é¢å', percentage: 22 }, |
| | | { type: 'åå·¥å', percentage: 15 } |
| | | ]) |
| | | |
| | | // å®ä»·å»ºè®® |
| | | const pricingSuggestions = ref([ |
| | | { |
| | | id: 1, |
| | | customerName: '山西ç¦åéå¢', |
| | | priority: 'high', |
| | | priorityText: 'é«ä¼å
级', |
| | | suggestedPrice: 1280, |
| | | negotiationSpace: 5.2, |
| | | timing: 'æ¬å¨å
' |
| | | }, |
| | | { |
| | | id: 2, |
| | | customerName: 'åè½çµå', |
| | | priority: 'medium', |
| | | priorityText: 'ä¸ä¼å
级', |
| | | suggestedPrice: 1250, |
| | | negotiationSpace: 3.8, |
| | | timing: 'ä¸å¨å' |
| | | }, |
| | | { |
| | | id: 3, |
| | | customerName: 'å®é¢éå¢', |
| | | priority: 'low', |
| | | priorityText: 'ä½ä¼å
级', |
| | | suggestedPrice: 1220, |
| | | negotiationSpace: 2.5, |
| | | timing: 'æ¬æå
' |
| | | } |
| | | ]) |
| | | |
| | | // çéç
¤å |
| | | const hotCoalTypes = ref([ |
| | | { |
| | | id: 1, |
| | | name: 'ä¼è´¨æ··ç
¤', |
| | | specification: 'åçé5500大å¡', |
| | | heatIndex: 9.2, |
| | | stockStatus: 'å
è¶³' |
| | | }, |
| | | { |
| | | id: 2, |
| | | name: 'ç²¾æ´ç¦ç
¤', |
| | | specification: 'ç°åâ¤8%', |
| | | heatIndex: 8.8, |
| | | stockStatus: 'å
è¶³' |
| | | }, |
| | | { |
| | | id: 3, |
| | | name: 'å¨åç
¤', |
| | | specification: 'åçé6000大å¡', |
| | | heatIndex: 8.5, |
| | | stockStatus: 'ç´§å¼ ' |
| | | } |
| | | ]) |
| | | |
| | | // 客æ·ç²æ§æåçç¥ |
| | | const loyaltyImprovements = ref([ |
| | | { |
| | | id: 1, |
| | | strategyName: 'å·®å¼åå®ä»·çç¥', |
| | | successRate: 85, |
| | | description: 'æ ¹æ®å®¢æ·éè´é¢æ¬¡å议价è½åï¼å¶å®ä¸ªæ§åä»·æ ¼æ¹æ¡' |
| | | }, |
| | | { |
| | | id: 2, |
| | | strategyName: 'ç²¾åæ¨åèå¥', |
| | | successRate: 78, |
| | | description: 'åºäºå®¢æ·éè´å¨æåæï¼å¨æä½³æ¶æºæ¨éç¸å
³äº§å' |
| | | }, |
| | | { |
| | | id: 3, |
| | | strategyName: 'å¢å¼æå¡å
', |
| | | successRate: 92, |
| | | description: 'æä¾ç©æµé
éãè´¨éæ£æµçå¢å¼æå¡ï¼æåå®¢æ·æ»¡æåº¦' |
| | | } |
| | | ]) |
| | | |
| | | // æ¨¡ææ°æ®çæ |
| | | const generateMockData = () => { |
| | | // çæä»·æ ¼è¶å¿æ°æ® |
| | | const dates = [] |
| | | const prices = [] |
| | | const volumes = [] |
| | | |
| | | for (let i = 30; i >= 0; i--) { |
| | | const date = new Date() |
| | | date.setDate(date.getDate() - i) |
| | | dates.push(date.toLocaleDateString()) |
| | | |
| | | const basePrice = 1200 + Math.random() * 200 |
| | | prices.push(basePrice) |
| | | |
| | | const baseVolume = 30 + Math.random() * 20 |
| | | volumes.push(baseVolume) |
| | | } |
| | | |
| | | return { dates, prices, volumes } |
| | | } |
| | | |
| | | // åå§åä»·æ ¼è¶å¿å¾è¡¨ |
| | | const initPriceChart = () => { |
| | | if (!priceChartRef.value) return |
| | | |
| | | priceChart = echarts.init(priceChartRef.value) |
| | | const { dates, prices, volumes } = generateMockData() |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'æ··ç
¤æåº¦ä»·æ ¼ååè¶å¿', |
| | | left: 'center', |
| | | textStyle: { |
| | | fontSize: 16, |
| | | fontWeight: 'bold' |
| | | } |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'cross' |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ['ä»·æ ¼(å
/å¨)', '交æé(ä¸å¨)'], |
| | | top: 30 |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: dates, |
| | | axisLabel: { |
| | | rotate: 45 |
| | | } |
| | | }, |
| | | yAxis: [ |
| | | { |
| | | type: 'value', |
| | | name: 'ä»·æ ¼(å
/å¨)', |
| | | position: 'left' |
| | | }, |
| | | { |
| | | type: 'value', |
| | | name: '交æé(ä¸å¨)', |
| | | position: 'right' |
| | | } |
| | | ], |
| | | series: [ |
| | | { |
| | | name: 'ä»·æ ¼(å
/å¨)', |
| | | type: 'line', |
| | | data: prices, |
| | | smooth: true, |
| | | lineStyle: { |
| | | color: '#409EFF', |
| | | width: 3 |
| | | }, |
| | | itemStyle: { |
| | | color: '#409EFF' |
| | | } |
| | | }, |
| | | { |
| | | name: '交æé(ä¸å¨)', |
| | | type: 'bar', |
| | | yAxisIndex: 1, |
| | | data: volumes, |
| | | itemStyle: { |
| | | color: '#67C23A' |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | |
| | | priceChart.setOption(option) |
| | | } |
| | | |
| | | // åå§å客æ·åå¸å¾è¡¨ |
| | | const initCustomerChart = () => { |
| | | if (!customerChartRef.value) return |
| | | |
| | | customerChart = echarts.init(customerChartRef.value) |
| | | |
| | | const option = { |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b}: {c} ({d}%)' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '客æ·ç±»å', |
| | | type: 'pie', |
| | | radius: ['40%', '70%'], |
| | | avoidLabelOverlap: false, |
| | | label: { |
| | | show: false, |
| | | position: 'center' |
| | | }, |
| | | emphasis: { |
| | | label: { |
| | | show: true, |
| | | fontSize: '18', |
| | | fontWeight: 'bold' |
| | | } |
| | | }, |
| | | labelLine: { |
| | | show: false |
| | | }, |
| | | data: [ |
| | | { value: 35, name: 'ç¦åå' }, |
| | | { value: 28, name: 'çµå' }, |
| | | { value: 22, name: 'é¢å' }, |
| | | { value: 15, name: 'åå·¥å' } |
| | | ] |
| | | } |
| | | ] |
| | | } |
| | | |
| | | customerChart.setOption(option) |
| | | } |
| | | |
| | | // åå§ååºå对æ¯å¾è¡¨ |
| | | const initRegionChart = () => { |
| | | if (!regionChartRef.value) return |
| | | |
| | | regionChart = echarts.init(regionChartRef.value) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'å产å°ç
¤ä»·å¯¹æ¯', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ['æ··ç
¤', 'ç²¾ç
¤', 'å¨åç
¤', 'ç¦ç
¤'], |
| | | top: 30 |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: ['山西', 'å
èå¤', 'é西', 'æ°ç'] |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'ä»·æ ¼(å
/å¨)' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'æ··ç
¤', |
| | | type: 'bar', |
| | | data: [1250, 1180, 1220, 1150] |
| | | }, |
| | | { |
| | | name: 'ç²¾ç
¤', |
| | | type: 'bar', |
| | | data: [1350, 1280, 1320, 1250] |
| | | }, |
| | | { |
| | | name: 'å¨åç
¤', |
| | | type: 'bar', |
| | | data: [1150, 1080, 1120, 1050] |
| | | }, |
| | | { |
| | | name: 'ç¦ç
¤', |
| | | type: 'bar', |
| | | data: [1450, 1380, 1420, 1350] |
| | | } |
| | | ] |
| | | } |
| | | |
| | | regionChart.setOption(option) |
| | | } |
| | | |
| | | // åå§åéè´å¨æå¾è¡¨ |
| | | const initCycleChart = () => { |
| | | if (!cycleChartRef.value) return |
| | | |
| | | cycleChart = echarts.init(cycleChartRef.value) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '客æ·éè´å¨æåå¸', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'éè´å¨æ', |
| | | type: 'funnel', |
| | | left: '10%', |
| | | top: 60, |
| | | bottom: 60, |
| | | width: '80%', |
| | | height: '80%', |
| | | min: 0, |
| | | max: 100, |
| | | minSize: '0%', |
| | | maxSize: '100%', |
| | | sort: 'descending', |
| | | gap: 2, |
| | | label: { |
| | | show: true, |
| | | position: 'inside' |
| | | }, |
| | | labelLine: { |
| | | length: 10, |
| | | lineStyle: { |
| | | width: 1, |
| | | type: 'solid' |
| | | } |
| | | }, |
| | | itemStyle: { |
| | | borderColor: '#fff', |
| | | borderWidth: 1 |
| | | }, |
| | | emphasis: { |
| | | label: { |
| | | fontSize: 20 |
| | | } |
| | | }, |
| | | data: [ |
| | | { value: 100, name: 'é«é¢å®¢æ·(å¨éè´)' }, |
| | | { value: 80, name: 'ä¸é¢å®¢æ·(æéè´)' }, |
| | | { value: 60, name: 'ä½é¢å®¢æ·(å£éè´)' }, |
| | | { value: 40, name: 'å¶å客æ·(å¹´éè´)' } |
| | | ] |
| | | } |
| | | ] |
| | | } |
| | | |
| | | cycleChart.setOption(option) |
| | | } |
| | | |
| | | // å·æ°æ°æ® |
| | | const refreshData = async () => { |
| | | refreshing.value = true |
| | | |
| | | try { |
| | | // æ¨¡ææ°æ®å·æ° |
| | | await new Promise(resolve => setTimeout(resolve, 2000)) |
| | | |
| | | // æ´æ°å¸åºæ°æ® |
| | | marketData.avgPrice = 1200 + Math.random() * 200 |
| | | marketData.priceChange = (Math.random() - 0.5) * 10 |
| | | marketData.totalVolume = 1000 + Math.random() * 500 |
| | | marketData.volumeChange = (Math.random() - 0.5) * 8 |
| | | marketData.activeCustomers = 140 + Math.floor(Math.random() * 40) |
| | | marketData.customerChange = (Math.random() - 0.5) * 6 |
| | | marketData.trendScore = 7 + Math.random() * 3 |
| | | |
| | | // æ´æ°æ¶é´ |
| | | lastUpdateTime.value = new Date().toLocaleString() |
| | | |
| | | // éæ°åå§åå¾è¡¨ |
| | | await nextTick() |
| | | initPriceChart() |
| | | initCustomerChart() |
| | | initRegionChart() |
| | | initCycleChart() |
| | | |
| | | ElMessage.success('æ°æ®å·æ°æå') |
| | | } catch (error) { |
| | | ElMessage.error('æ°æ®å·æ°å¤±è´¥') |
| | | } finally { |
| | | refreshing.value = false |
| | | } |
| | | } |
| | | |
| | | // èªå¨å·æ°å®æ¶å¨ |
| | | let refreshTimer = null |
| | | |
| | | // å¯å¨èªå¨å·æ° |
| | | const startAutoRefresh = () => { |
| | | refreshTimer = setInterval(() => { |
| | | refreshData() |
| | | }, 10 * 60 * 1000) // 10åé |
| | | } |
| | | |
| | | // 忢èªå¨å·æ° |
| | | const stopAutoRefresh = () => { |
| | | if (refreshTimer) { |
| | | clearInterval(refreshTimer) |
| | | refreshTimer = null |
| | | } |
| | | } |
| | | |
| | | // çå¬çªå£å¤§å°åå |
| | | const handleResize = () => { |
| | | if (priceChart) priceChart.resize() |
| | | if (customerChart) customerChart.resize() |
| | | if (regionChart) regionChart.resize() |
| | | if (cycleChart) cycleChart.resize() |
| | | } |
| | | |
| | | // çå½å¨æ |
| | | onMounted(async () => { |
| | | // åå§åæ¶é´ |
| | | lastUpdateTime.value = new Date().toLocaleString() |
| | | |
| | | // çå¾
DOM渲æå®æ |
| | | await nextTick() |
| | | |
| | | // åå§åå¾è¡¨ |
| | | initPriceChart() |
| | | initCustomerChart() |
| | | initRegionChart() |
| | | initCycleChart() |
| | | |
| | | // å¯å¨èªå¨å·æ° |
| | | startAutoRefresh() |
| | | |
| | | // çå¬çªå£å¤§å°åå |
| | | window.addEventListener('resize', handleResize) |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | // 忢èªå¨å·æ° |
| | | stopAutoRefresh() |
| | | |
| | | // 鿝å¾è¡¨ |
| | | if (priceChart) priceChart.dispose() |
| | | if (customerChart) customerChart.dispose() |
| | | if (regionChart) regionChart.dispose() |
| | | if (cycleChart) cycleChart.dispose() |
| | | |
| | | // ç§»é¤äºä»¶çå¬ |
| | | window.removeEventListener('resize', handleResize) |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .market-analysis-container { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background: white; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .page-header h1 { |
| | | margin: 0; |
| | | color: #303133; |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .header-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .update-time { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .data-overview { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .overview-card { |
| | | height: 120px; |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 100%; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 60px; |
| | | height: 60px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 15px; |
| | | font-size: 24px; |
| | | color: white; |
| | | } |
| | | |
| | | .price-icon { |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | } |
| | | |
| | | .volume-icon { |
| | | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| | | } |
| | | |
| | | .customer-icon { |
| | | background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
| | | } |
| | | |
| | | .trend-icon { |
| | | background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); |
| | | } |
| | | |
| | | .card-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-title { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .card-value { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .card-change { |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .card-change.positive { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .card-change.negative { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .main-analysis { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .analysis-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | font-weight: 600; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .header-controls { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .chart-container { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .chart { |
| | | width: 100%; |
| | | } |
| | | |
| | | .customer-analysis h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .customer-type-distribution { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .preference-item { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .preference-label { |
| | | width: 80px; |
| | | font-size: 12px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .preference-bar { |
| | | flex: 1; |
| | | height: 8px; |
| | | background-color: #f0f0f0; |
| | | border-radius: 4px; |
| | | margin: 0 10px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .bar-fill { |
| | | height: 100%; |
| | | background: linear-gradient(90deg, #409eff 0%, #67c23a 100%); |
| | | border-radius: 4px; |
| | | transition: width 0.3s ease; |
| | | } |
| | | |
| | | .preference-value { |
| | | width: 40px; |
| | | font-size: 12px; |
| | | color: #409eff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .detail-analysis { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .smart-recommendations { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .recommendations-content { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .recommendation-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #303133; |
| | | font-size: 14px; |
| | | border-bottom: 2px solid #409eff; |
| | | padding-bottom: 5px; |
| | | } |
| | | |
| | | .suggestion-item { |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | padding: 12px; |
| | | margin-bottom: 12px; |
| | | border-left: 4px solid #409eff; |
| | | } |
| | | |
| | | .suggestion-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .customer-name { |
| | | font-weight: 500; |
| | | color: #303133; |
| | | } |
| | | |
| | | .suggestion-content p { |
| | | margin: 5px 0; |
| | | font-size: 12px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .coal-type-item { |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | padding: 12px; |
| | | margin-bottom: 12px; |
| | | border-left: 4px solid #67c23a; |
| | | } |
| | | |
| | | .coal-info { |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .coal-name { |
| | | font-weight: 500; |
| | | color: #303133; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .coal-spec { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .coal-metrics { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .metric { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .metric .label { |
| | | color: #909399; |
| | | } |
| | | |
| | | .metric .value { |
| | | color: #409eff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .improvement-item { |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | padding: 12px; |
| | | margin-bottom: 12px; |
| | | border-left: 4px solid #e6a23c; |
| | | } |
| | | |
| | | .improvement-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .strategy-name { |
| | | font-weight: 500; |
| | | color: #303133; |
| | | } |
| | | |
| | | .success-rate { |
| | | font-size: 12px; |
| | | color: #67c23a; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .improvement-content p { |
| | | margin: 5px 0 10px 0; |
| | | font-size: 12px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | gap: 8px; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 1200px) { |
| | | .header-controls { |
| | | flex-direction: column; |
| | | gap: 5px; |
| | | } |
| | | |
| | | .header-controls .el-select { |
| | | width: 100px !important; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .page-header { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .header-info { |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | </style> |