Compare commits
7 Commits
431888797d
...
6719fe8600
Author | SHA1 | Date |
---|---|---|
|
6719fe8600 | |
|
d16e99c63c | |
|
aafd49217c | |
|
226e4d0020 | |
|
9839a4421f | |
|
0b783c70c3 | |
|
873563de73 |
|
@ -4,197 +4,294 @@
|
|||
<div v-if="loading" class="loading-mask">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="chartRef"
|
||||
:style="{
|
||||
width: width,
|
||||
|
||||
<div
|
||||
ref="chartRef"
|
||||
:style="{
|
||||
width: width,
|
||||
height: height,
|
||||
minHeight: '100px'
|
||||
}"
|
||||
}"
|
||||
class="echarts-container"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount, watch, computed } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
export default {
|
||||
name: 'EChartsComponent',
|
||||
props: {
|
||||
// 图表配置项
|
||||
options: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
// 图表主题
|
||||
theme: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图表宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
// 图表高度
|
||||
height: {
|
||||
type: String,
|
||||
default: '400px'
|
||||
},
|
||||
// 是否自动调整大小
|
||||
autoResize: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 加载状态
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 动画时长
|
||||
animationDuration: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
// 是否平滑曲线(用于折线图)
|
||||
smooth: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图表类型
|
||||
chartType: {
|
||||
type: String,
|
||||
default: 'line',
|
||||
validator: (value) => ['line', 'bar', 'pie', 'scatter'].includes(value)
|
||||
}
|
||||
// Props 定义
|
||||
const props = defineProps({
|
||||
// 图表数据
|
||||
data: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
emits: ['chartReady', 'click', 'legendselectchanged'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null)
|
||||
let chartInstance = null
|
||||
// 图表类型配置
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图表主题
|
||||
theme: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图表宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
// 图表高度
|
||||
height: {
|
||||
type: String,
|
||||
default: '400px'
|
||||
},
|
||||
// 是否自动调整大小
|
||||
autoResize: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 加载状态
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 动画时长
|
||||
animationDuration: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
// 是否平滑曲线(用于折线图)
|
||||
smooth: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图表类型
|
||||
chartType: {
|
||||
type: String,
|
||||
default: 'line',
|
||||
validator: (value) => ['line', 'bar', 'pie', 'scatter'].includes(value)
|
||||
},
|
||||
// tooltip 显示的系列名称数组,为空则显示全部
|
||||
tooltipSeries: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 自定义 tooltip 格式化函数
|
||||
tooltipFormatter: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
// 处理配置项
|
||||
const processOptions = (options) => {
|
||||
const defaultOptions = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
// Emits 定义
|
||||
const emit = defineEmits(['chartReady', 'click', 'legendselectchanged'])
|
||||
|
||||
const chartRef = ref(null)
|
||||
let chartInstance = null
|
||||
|
||||
// 默认配置
|
||||
const defaultConfig = {
|
||||
xField: 'name',
|
||||
series: [
|
||||
{
|
||||
name: '数值',
|
||||
field: 'value',
|
||||
type: 'bar'
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
name: '',
|
||||
min: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 合并配置
|
||||
const mergedConfig = computed(() => ({
|
||||
...defaultConfig,
|
||||
...props.config
|
||||
}))
|
||||
|
||||
// 生成图表配置
|
||||
const generateOptions = computed(() => {
|
||||
if (!props.data?.length) return {}
|
||||
|
||||
const config = mergedConfig.value
|
||||
const categories = props.data.map(item => item[config.xField])
|
||||
|
||||
// 处理系列数据
|
||||
const series = config.series
|
||||
.filter(s => s.show !== false) // 过滤掉 show: false 的系列
|
||||
.map(s => ({
|
||||
name: s.name,
|
||||
type: s.type,
|
||||
yAxisIndex: s.yAxisIndex || 0,
|
||||
data: props.data.map(item => item[s.field]),
|
||||
color: s.color,
|
||||
barMaxWidth: 50,
|
||||
barGap: '30%',
|
||||
itemStyle: {
|
||||
color: s.color,
|
||||
borderRadius: s.type === 'bar' ? [4, 4, 0, 0] : 0
|
||||
},
|
||||
label: s.showLabel ? {
|
||||
show: true,
|
||||
position: s.type === 'line' ? 'top' : 'inside',
|
||||
formatter: s.labelFormatter || '{c}',
|
||||
fontSize: 12,
|
||||
color: s.type === 'line' ? s.color : '#fff'
|
||||
} : undefined,
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
}
|
||||
}))
|
||||
|
||||
return {
|
||||
title: props.title ? {
|
||||
text: props.title
|
||||
} : null,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
},
|
||||
formatter: (params) => {
|
||||
// 找到原始配置中对应的系列配置
|
||||
const validParams = params.filter(param => {
|
||||
const seriesConfig = config.series.find(s => s.name === param.seriesName)
|
||||
return seriesConfig && seriesConfig.tooltip?.show !== false
|
||||
})
|
||||
|
||||
if (validParams.length === 0) return ''
|
||||
|
||||
let result = `${validParams[0].axisValue}<br/>`
|
||||
validParams.forEach(param => {
|
||||
// 找到对应的系列配置
|
||||
const seriesConfig = config.series.find(s => s.name === param.seriesName)
|
||||
// 提取 labelFormatter 中的单位
|
||||
let unit = ''
|
||||
if (seriesConfig?.labelFormatter) {
|
||||
const match = seriesConfig.labelFormatter.match(/\{c\}(.+)/)
|
||||
if (match) {
|
||||
unit = match[1]
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
right: '3%',
|
||||
left: '3%',
|
||||
bottom: '3%',
|
||||
top: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
animation: true,
|
||||
animationDuration: props.animationDuration
|
||||
result += `${param.marker}${param.seriesName}: ${param.value}${unit}<br/>`
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 处理折线图的平滑设置
|
||||
if (props.chartType === 'line' && options.series) {
|
||||
options.series = options.series.map(series => ({
|
||||
...series,
|
||||
smooth: props.smooth
|
||||
}))
|
||||
},
|
||||
legend: {
|
||||
data: series.map(s => s.name)
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: categories,
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
rotate: categories.length > 5 ? 30 : 0
|
||||
}
|
||||
|
||||
return {
|
||||
...defaultOptions,
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return
|
||||
|
||||
chartInstance = echarts.init(chartRef.value, props.theme)
|
||||
|
||||
// 绑定事件
|
||||
chartInstance.on('click', (params) => {
|
||||
emit('click', params)
|
||||
})
|
||||
|
||||
chartInstance.on('legendselectchanged', (params) => {
|
||||
emit('legendselectchanged', params)
|
||||
})
|
||||
|
||||
const processedOptions = processOptions(props.options)
|
||||
chartInstance.setOption(processedOptions)
|
||||
emit('chartReady', chartInstance)
|
||||
}
|
||||
|
||||
// 更新图表
|
||||
const updateChart = () => {
|
||||
if (!chartInstance) return
|
||||
|
||||
const processedOptions = processOptions(props.options)
|
||||
chartInstance.setOption(processedOptions)
|
||||
}
|
||||
|
||||
// 调整图表大小
|
||||
const resizeChart = debounce(() => {
|
||||
if (!chartInstance) return
|
||||
chartInstance.resize()
|
||||
}, 100)
|
||||
|
||||
// 监听配置变化
|
||||
watch(
|
||||
() => props.options,
|
||||
() => updateChart(),
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 监听主题变化
|
||||
watch(
|
||||
() => props.theme,
|
||||
() => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose()
|
||||
}
|
||||
initChart()
|
||||
}
|
||||
)
|
||||
|
||||
// 监听加载状态
|
||||
watch(
|
||||
() => props.loading,
|
||||
(val) => {
|
||||
if (chartInstance) {
|
||||
val ? chartInstance.showLoading() : chartInstance.hideLoading()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
initChart()
|
||||
if (props.autoResize) {
|
||||
window.addEventListener('resize', resizeChart)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (props.autoResize) {
|
||||
window.removeEventListener('resize', resizeChart)
|
||||
}
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose()
|
||||
chartInstance = null
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
chartRef
|
||||
},
|
||||
yAxis: config.yAxis.map((axis, index) => ({
|
||||
type: 'value',
|
||||
name: axis.name,
|
||||
min: axis.min,
|
||||
max: axis.max,
|
||||
position: index === 0 ? 'left' : 'right',
|
||||
splitLine: {
|
||||
show: index === 0
|
||||
},
|
||||
axisLabel: axis.axisLabel ? {
|
||||
formatter: axis.axisLabel
|
||||
} : undefined
|
||||
})),
|
||||
series,
|
||||
grid: {
|
||||
top: '15%',
|
||||
bottom: '15%',
|
||||
left: '10%',
|
||||
right: '10%',
|
||||
containLabel: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return
|
||||
|
||||
chartInstance = echarts.init(chartRef.value, props.theme)
|
||||
chartInstance.setOption(generateOptions.value)
|
||||
emit('chartReady', chartInstance)
|
||||
}
|
||||
|
||||
// 更新图表
|
||||
const updateChart = () => {
|
||||
if (!chartInstance) return
|
||||
chartInstance.setOption(generateOptions.value)
|
||||
}
|
||||
|
||||
// 调整图表大小
|
||||
const resizeChart = debounce(() => {
|
||||
if (!chartInstance) return
|
||||
chartInstance.resize()
|
||||
}, 100)
|
||||
|
||||
// 监听配置变化
|
||||
watch(
|
||||
() => [props.data, props.config],
|
||||
() => updateChart(),
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 监听主题变化
|
||||
watch(
|
||||
() => props.theme,
|
||||
() => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose()
|
||||
}
|
||||
initChart()
|
||||
}
|
||||
)
|
||||
|
||||
// 监听加载状态
|
||||
watch(
|
||||
() => props.loading,
|
||||
(val) => {
|
||||
if (chartInstance) {
|
||||
val ? chartInstance.showLoading() : chartInstance.hideLoading()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
initChart()
|
||||
if (props.autoResize) {
|
||||
window.addEventListener('resize', resizeChart)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (props.autoResize) {
|
||||
window.removeEventListener('resize', resizeChart)
|
||||
}
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose()
|
||||
chartInstance = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -233,4 +330,4 @@ export default {
|
|||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -52,28 +52,32 @@
|
|||
|
||||
<!-- 分页区域 -->
|
||||
<div class="pagination-section">
|
||||
<el-pagination
|
||||
background
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
<el-pagination
|
||||
background
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="1000"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="chart-demo">
|
||||
<!-- 柱状图示例 -->
|
||||
<ECharts
|
||||
:options="barChartOptions"
|
||||
<!-- 图表区域 -->
|
||||
<div class="chart-section">
|
||||
<ECharts
|
||||
:data="salesData"
|
||||
:config="chartConfig"
|
||||
title="销量对比"
|
||||
height="300px"
|
||||
chartType="bar"
|
||||
:loading="loading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import ECharts from '@/components/ECharts/index.vue'
|
||||
import { useSalesChart } from './composables/useSalesChart'
|
||||
|
||||
const value = ref('')
|
||||
const options = [
|
||||
|
@ -229,234 +233,170 @@ const getLeafIds = (node, visited) => {
|
|||
return ids;
|
||||
};
|
||||
|
||||
// 定义图表数据结构
|
||||
const chartData = ref({
|
||||
categories: ['手机', '电脑', '平板', '耳机', '手表'],
|
||||
// 图表相关
|
||||
const { salesData, chartConfig, loading } = useSalesChart()
|
||||
|
||||
// 2. 用户增长趋势数据
|
||||
const userGrowthData = ref([
|
||||
{ month: '2023-04', newUsers: 1500, activeUsers: 12000, retention: 85 },
|
||||
{ month: '2023-05', newUsers: 1800, activeUsers: 13200, retention: 87 },
|
||||
{ month: '2023-06', newUsers: 2200, activeUsers: 14800, retention: 88 },
|
||||
{ month: '2023-07', newUsers: 2100, activeUsers: 15900, retention: 86 },
|
||||
{ month: '2023-08', newUsers: 2600, activeUsers: 17200, retention: 89 },
|
||||
{ month: '2023-09', newUsers: 2800, activeUsers: 18900, retention: 90 },
|
||||
{ month: '2023-10', newUsers: 3100, activeUsers: 21000, retention: 91 },
|
||||
{ month: '2023-11', newUsers: 3400, activeUsers: 23500, retention: 89 },
|
||||
{ month: '2023-12', newUsers: 3800, activeUsers: 26000, retention: 92 },
|
||||
{ month: '2024-01', newUsers: 4200, activeUsers: 29000, retention: 93 },
|
||||
{ month: '2024-02', newUsers: 4500, activeUsers: 32000, retention: 91 },
|
||||
{ month: '2024-03', newUsers: 4800, activeUsers: 35000, retention: 92 }
|
||||
])
|
||||
|
||||
// 用户增长趋势配置
|
||||
const userGrowthConfig = {
|
||||
xField: 'month',
|
||||
series: [
|
||||
{
|
||||
name: '上月销量',
|
||||
name: '新增用户',
|
||||
field: 'newUsers',
|
||||
type: 'bar',
|
||||
data: [120, 200, 150, 80, 70],
|
||||
color: '#409EFF',
|
||||
showLabel: true,
|
||||
labelFormatter: '{c}台'
|
||||
},
|
||||
{
|
||||
name: '本月销量',
|
||||
type: 'bar',
|
||||
data: [140, 180, 180, 120, 90],
|
||||
color: '#67C23A',
|
||||
showLabel: true,
|
||||
labelFormatter: '{c}台'
|
||||
labelFormatter: '{c}人'
|
||||
},
|
||||
{
|
||||
name: '环比增长',
|
||||
name: '活跃用户',
|
||||
field: 'activeUsers',
|
||||
type: 'line',
|
||||
color: '#409EFF',
|
||||
showLabel: true,
|
||||
yAxisIndex: 1,
|
||||
data: [16.7, -10, 20, 50, 28.6],
|
||||
labelFormatter: '{c}'
|
||||
},
|
||||
{
|
||||
name: '留存率',
|
||||
field: 'retention',
|
||||
type: 'line',
|
||||
color: '#E6A23C',
|
||||
showLabel: true,
|
||||
yAxisIndex: 2,
|
||||
labelFormatter: '{c}%'
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
name: '销量',
|
||||
min: 0,
|
||||
nameTextStyle: {
|
||||
padding: [0, 0, 0, 50]
|
||||
}
|
||||
name: '新增用户',
|
||||
min: 0
|
||||
},
|
||||
{
|
||||
name: '增长率',
|
||||
name: '活跃用户',
|
||||
min: 0,
|
||||
position: 'right'
|
||||
},
|
||||
{
|
||||
name: '留存率',
|
||||
min: 80,
|
||||
max: 100,
|
||||
axisLabel: '{value}%',
|
||||
nameTextStyle: {
|
||||
padding: [0, 0, 0, 50]
|
||||
position: 'right',
|
||||
offset: 80,
|
||||
axisLabel: '{value}%'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 3. 市场份额数据
|
||||
const marketShareData = ref([
|
||||
{ category: '手机', value: 35, lastYear: 32 },
|
||||
{ category: '电脑', value: 25, lastYear: 28 },
|
||||
{ category: '平板', value: 20, lastYear: 18 },
|
||||
{ category: '智能手表', value: 12, lastYear: 10 },
|
||||
{ category: '耳机', value: 8, lastYear: 12 }
|
||||
])
|
||||
|
||||
// 市场份额配置
|
||||
const marketShareConfig = {
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
field: 'value',
|
||||
name: '市场份额',
|
||||
radius: ['50%', '70%'],
|
||||
center: ['50%', '50%'],
|
||||
showLabel: true,
|
||||
labelFormatter: '{b}: {c}%',
|
||||
itemStyle: {
|
||||
borderRadius: 6
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 生成图表配置的函数
|
||||
const generateChartOptions = (chartData) => {
|
||||
const barSeries = chartData.series.filter(item => item.type === 'bar')
|
||||
const lineSeries = chartData.series.filter(item => item.type === 'line')
|
||||
|
||||
return {
|
||||
title: {
|
||||
text: '各类商品销量对比'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
},
|
||||
formatter: function(params) {
|
||||
let result = `${params[0].axisValue}<br/>`;
|
||||
// 计算同比增长
|
||||
const currentValue = params.find(p => p.seriesName === '本月销量')?.value || 0;
|
||||
const lastValue = params.find(p => p.seriesName === '上月销量')?.value || 0;
|
||||
const growth = params.find(p => p.seriesName === '环比增长')?.value || 0;
|
||||
|
||||
// 添加所有系列的数据
|
||||
params.forEach(param => {
|
||||
const marker = param.marker;
|
||||
const seriesName = param.seriesName;
|
||||
const value = param.value;
|
||||
const unit = param.seriesName.includes('增长') ? '%' : '台';
|
||||
result += `${marker}${seriesName}: ${value}${unit}<br/>`;
|
||||
});
|
||||
|
||||
// 添加额外统计信息
|
||||
if (currentValue && lastValue) {
|
||||
const diff = currentValue - lastValue;
|
||||
result += '<br/>';
|
||||
result += `<span style="color: #666">销量差额: ${diff > 0 ? '+' : ''}${diff}台</span><br/>`;
|
||||
result += `<span style="color: ${growth >= 0 ? '#67C23A' : '#F56C6C'}">环比: ${growth}%</span>`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: chartData.series.map(item => item.name),
|
||||
selected: chartData.series.reduce((acc, item) => {
|
||||
acc[item.name] = true;
|
||||
return acc;
|
||||
}, {})
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: chartData.categories,
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
rotate: chartData.categories.length > 5 ? 30 : 0
|
||||
}
|
||||
},
|
||||
yAxis: chartData.yAxis.map((axis, index) => ({
|
||||
type: 'value',
|
||||
name: axis.name,
|
||||
min: axis.min,
|
||||
max: axis.max,
|
||||
position: index === 0 ? 'left' : 'right',
|
||||
splitLine: {
|
||||
show: index === 0
|
||||
},
|
||||
axisLabel: axis.axisLabel ? {
|
||||
formatter: axis.axisLabel
|
||||
} : undefined
|
||||
})),
|
||||
series: chartData.series.map(item => ({
|
||||
name: item.name,
|
||||
type: item.type,
|
||||
yAxisIndex: item.yAxisIndex || 0,
|
||||
data: item.data,
|
||||
barMaxWidth: 50, // 限制柱子最大宽度
|
||||
barGap: '30%', // 柱子之间的间距
|
||||
itemStyle: {
|
||||
color: item.color,
|
||||
borderRadius: item.type === 'bar' ? [4, 4, 0, 0] : 0
|
||||
},
|
||||
label: item.showLabel ? {
|
||||
show: true,
|
||||
position: item.type === 'line' ? 'top' : 'inside',
|
||||
formatter: item.labelFormatter,
|
||||
fontSize: 12,
|
||||
color: item.type === 'line' ? item.color : '#fff'
|
||||
} : undefined,
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
label: {
|
||||
show: true,
|
||||
position: item.type === 'line' ? 'top' : 'inside',
|
||||
formatter: item.labelFormatter || '{c}',
|
||||
fontSize: 12,
|
||||
color: item.type === 'line' ? item.color : '#fff'
|
||||
}
|
||||
}
|
||||
})),
|
||||
grid: {
|
||||
top: '15%',
|
||||
bottom: '15%',
|
||||
left: '10%',
|
||||
right: '10%',
|
||||
containLabel: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成图表配置
|
||||
const barChartOptions = computed(() => generateChartOptions(chartData.value))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.test-center {
|
||||
padding: 16px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
|
||||
.search-section {
|
||||
background-color: #fff;
|
||||
.test-center {
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
|
||||
.operation-section {
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.search-section {
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.left-area {
|
||||
.operation-section {
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.left-area {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.right-area {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.right-area {
|
||||
.table-section {
|
||||
background-color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 12px;
|
||||
|
||||
:deep(.el-table) {
|
||||
// 移除表格的外边框
|
||||
--el-table-border: none;
|
||||
// 减小表格行的上下padding
|
||||
--el-table-row-height: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-section {
|
||||
background-color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.table-section {
|
||||
background-color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 12px;
|
||||
|
||||
:deep(.el-table) {
|
||||
// 移除表格的外边框
|
||||
--el-table-border: none;
|
||||
// 减小表格行的上下padding
|
||||
--el-table-row-height: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-section {
|
||||
background-color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-demo {
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
import { ref, onMounted } from 'vue'
|
||||
|
||||
export function useSalesChart() {
|
||||
// 销量对比数据
|
||||
const salesData = ref([])
|
||||
const loading = ref(true) // 添加 loading 状态
|
||||
|
||||
// 模拟异步数据加载
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 模拟 API 延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||
|
||||
salesData.value = [
|
||||
{
|
||||
productName: '手机',
|
||||
lastMonthSales: 120,
|
||||
currentMonthSales: 140,
|
||||
growthRate: 16.7
|
||||
},
|
||||
{
|
||||
productName: '耳机',
|
||||
lastMonthSales: 120,
|
||||
currentMonthSales: 140,
|
||||
growthRate: 16.7
|
||||
},
|
||||
{
|
||||
productName: '手机',
|
||||
lastMonthSales: 120,
|
||||
currentMonthSales: 140,
|
||||
growthRate: 16.7
|
||||
},
|
||||
{
|
||||
productName: '手机',
|
||||
lastMonthSales: 120,
|
||||
currentMonthSales: 140,
|
||||
growthRate: 16.7
|
||||
},
|
||||
// ... 其他数据
|
||||
]
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
// 图表配置
|
||||
const chartConfig = {
|
||||
xField: 'productName',
|
||||
series: [
|
||||
{
|
||||
name: '上月销量',
|
||||
field: 'lastMonthSales',
|
||||
type: 'bar',
|
||||
color: '#409EFF',
|
||||
showLabel: true,
|
||||
labelFormatter: '{c}台',
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
name: '本月销量',
|
||||
field: 'currentMonthSales',
|
||||
type: 'bar',
|
||||
color: '#67C23A',
|
||||
showLabel: true,
|
||||
labelFormatter: '{c}台',
|
||||
show: true,
|
||||
tooltip: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '环比增长',
|
||||
field: 'growthRate',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
color: '#E6A23C',
|
||||
showLabel: true,
|
||||
labelFormatter: '{c}%',
|
||||
tooltip: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
name: '销量',
|
||||
min: 0
|
||||
},
|
||||
{
|
||||
name: '增长率',
|
||||
min: 0,
|
||||
max: 100,
|
||||
axisLabel: '{value}%'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return {
|
||||
salesData,
|
||||
chartConfig,
|
||||
loading
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue