1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153 |
- <template>
- <div class="app-container">
- <!-- 筛选 -->
- <div class="filter-card">
- <h1>{{ this.params.category }} 销量一览</h1>
- <div class="filter-item">
- <label>日期范围:</label>
- <el-date-picker
- v-model="dateRange"
- type="daterange"
- range-separator="至"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- format="yyyy-MM-dd"
- value-format="yyyy-MM-dd"
- style="width: 380px"
- size="small"
- :picker-options="datePickerOptions"
- @change="handleDateChange"
- />
- </div>
- </div>
-
- <div class="filter-section">
- <div v-for="(item, key) in filterOptions" :key="key" class="filter-cell">
- <div class="cell-header">
- <span class="filter-title">{{ key | formatTitle }}</span>
- <el-button size="mini" type="text" @click="resetFilter(key)">
- 重置
- </el-button>
- </div>
- <div class="cell-content">
- <!-- 多选模式:复选框组 -->
- <div class="checkbox-group">
- <el-checkbox
- v-for="option in item"
- :key="option"
- v-model="selectedValues[key]"
- :label="option"
- :disabled="isOptionDisabled(key, option)"
- size="small"
- :class="{
- 'available-option': !isOptionDisabled(key, option),
- 'disabled-option': isOptionDisabled(key, option)
- }"
- @change="handleMultipleSelect(key, selectedValues[key])"
- >
- {{ option }}
- </el-checkbox>
- </div>
- </div>
- </div>
- </div>
-
- <div class="data-section">
- <div class="table-chart-data">
- <div class="table-chart-data-header">
- <span>月度数据</span>
- </div>
- <div class="table-chart-data-content">
- <div class="table-card">
- <el-table
- :data="monthData"
- style="width: 100%"
- show-summary
- :height="300"
- size="mini"
- border
- >
- <el-table-column prop="month" label="日付" />
- <el-table-column prop="totalQuantity" label="販売数量" />
- <el-table-column
- prop="totalAmount"
- label="販売金額"
- :formatter="formatAmount"
- />
- </el-table>
- </div>
- <div id="monthSalesBarChart" class="chart-card" style="height: 300px;" />
- </div>
- </div>
- <div v-for="(item, key) in moreData" :key="key" class="table-chart-data">
- <div class="table-chart-data-header">
- <span>{{ key | formatTitle }}</span>
- </div>
- <div class="table-chart-data-content">
- <div class="table-card">
- <el-table
- :data="item"
- style="width: 100%"
- show-summary
- size="mini"
- border
- :height="300"
- >
- <el-table-column
- v-if="key === 'customer_name'"
- prop="customer_name"
- label="客户"
- />
- <el-table-column
- v-if="key === 'product_code'"
- prop="product_code"
- label="商品コード"
- />
- <el-table-column
- v-if="key === 'brand'"
- prop="brand"
- label="品牌"
- />
- <el-table-column prop="totalQuantity" label="販売数量" />
- <el-table-column
- prop="totalAmount"
- label="販売金額"
- :formatter="formatAmount"
- />
- </el-table>
- </div>
- <div
- :id="getChartId(key)"
- class="chart-card"
- style="height: 300px;"
- />
- </div>
- </div>
- <div v-for="(item, key, index) in specsData" :key="key" class="table-chart-data">
- <div class="table-chart-data-header">
- <span>{{ key | formatTitle }}</span>
- </div>
- <div class="table-chart-data-content">
- <div class="table-card">
- <el-table
- :data="formatSpecsData(item)"
- style="width: 100%"
- show-summary
- :height="300"
- size="mini"
- border
- >
- <el-table-column prop="name" :label="key" />
- <el-table-column prop="totalQuantity" label="販売数量" />
- <el-table-column
- prop="totalAmount"
- label="販売金額"
- :formatter="formatAmount"
- />
- </el-table>
- </div>
- <div
- :id="getSpecsChartId(key, index)"
- class="chart-card"
- style="height: 300px;"
- />
- </div>
- </div>
- </div>
- </div>
- </template>
-
- <script>
- import * as echarts from 'echarts'
- import {
- getAnalysisAllFilterOptions,
- getAnalysisProductAnalysis,
- getAnalysisMonthlyAnalysis,
- getAnalysisComprehensiveAnalysis
- } from '@/api/sales-analysis'
- export default {
- name: 'RamAnalysis',
- components: {},
- filters: {
- formatTitle(key) {
- if (key === 'product_code') {
- return '商品コード'
- } else if (key === 'shop_name') {
- return '販売店舗'
- } else if (key === 'brand') {
- return '品牌'
- } else if (key === 'customer_name') {
- return '客户'
- }
- return key
- }
- },
- data() {
- return {
- loading: false,
- dateRange: null,
- filters: {
- productCode: ''
- },
- // 筛选选项
- filterOptions: {},
- // 绑定筛选选项
- filterBindOptions: [],
- // 所有筛选项都使用多选模式
- // 每个筛选项的选中值
- selectedValues: {},
-
- params: {
- startDate: '2025-08-01',
- endDate: '2025-12-31',
- category: 'SSD'
- },
- datePickerOptions: {},
- // 月度数据
- monthData: [],
- // 更多数据
- moreData: [],
- // 规格数据
- specsData: {}
- }
- },
- computed: {
- /**
- * 计算每个筛选项的可用选项
- * 根据当前选择和其他筛选项的绑定关系,确定哪些选项是可选的
- */
- availableOptions() {
- const result = {}
-
- // 获取当前所有筛选项的选择状态
- const currentSelections = {}
- Object.keys(this.filterOptions).forEach((key) => {
- const value = this.selectedValues[key]
- if (
- value !== null &&
- value !== undefined &&
- Array.isArray(value) &&
- value.length > 0
- ) {
- currentSelections[key] = value
- }
- })
-
- // 为每个筛选项计算可用选项
- Object.keys(this.filterOptions).forEach((filterKey) => {
- const options = this.filterOptions[filterKey]
- const available = []
-
- options.forEach((option) => {
- // 检查这个选项在当前选择下是否可用
- if (this.isOptionAvailable(filterKey, option, currentSelections)) {
- available.push(option)
- }
- })
-
- result[filterKey] = available
- })
-
- return result
- }
- },
- watch: {
- route: {
- handler(newVal) {
- if (newVal.query.category) {
- this.params.category = newVal.query.category
- this.fetchData()
- }
- }
- },
- specsData: {
- handler(newVal) {
- console.log('specsData变化:', newVal)
- if (newVal && Object.keys(newVal).length > 0) {
- this.$nextTick(() => {
- setTimeout(() => {
- Object.keys(newVal).forEach((key, index) => {
- this.renderSpecsPieChart(key, `specsPieChart${index}`)
- })
- }, 200)
- })
- }
- },
- deep: true
- }
- },
- mounted() {
- this.buildDatePickerOptions()
- this.fetchData()
- this.getData()
- },
-
- methods: {
- // 转换为千位符
- formatAmount(row) {
- return row.totalAmount.toLocaleString('ja-JP')
- },
-
- /**
- * 格式化规格数据,将嵌套对象转换为表格数据格式
- * @param {Object} specsData - 规格数据对象
- * @returns {Array} 格式化后的数组数据
- */
- formatSpecsData(specsData) {
- if (!specsData || typeof specsData !== 'object') {
- return []
- }
-
- return Object.keys(specsData).map((name) => ({
- name: name,
- totalQuantity: specsData[name].totalQuantity || 0,
- totalAmount: specsData[name].totalAmount || 0
- }))
- },
- /**
- * 构建日期选择器选项:禁用未来时间 + 快捷范围
- */
- buildDatePickerOptions() {
- const end = new Date()
- const startOfMonth = new Date(end.getFullYear(), end.getMonth(), 1)
- this.datePickerOptions = {
- disabledDate(time) {
- const today = new Date()
- // 禁用未来日期(大于今天)
- return time.getTime() > today.getTime()
- },
- shortcuts: [
- {
- text: '1年',
- onClick: (picker) => {
- const endDate = new Date()
- const startDate = new Date()
- startDate.setFullYear(startDate.getFullYear() - 1)
- picker.$emit('pick', [formatDate(startDate), formatDate(endDate)])
- }
- },
- {
- text: '6个月',
- onClick: (picker) => {
- const endDate = new Date()
- const startDate = new Date()
- startDate.setMonth(startDate.getMonth() - 6)
- picker.$emit('pick', [formatDate(startDate), formatDate(endDate)])
- }
- },
- {
- text: '3个月',
- onClick: (picker) => {
- const endDate = new Date()
- const startDate = new Date()
- startDate.setMonth(startDate.getMonth() - 3)
- picker.$emit('pick', [formatDate(startDate), formatDate(endDate)])
- }
- },
- {
- text: '1个月',
- onClick: (picker) => {
- const endDate = new Date()
- const startDate = new Date()
- startDate.setMonth(startDate.getMonth() - 1)
- picker.$emit('pick', [formatDate(startDate), formatDate(endDate)])
- }
- }
- ]
- }
-
- // 如果当前没有选择日期,默认设置为本月初至今天
- if (!this.dateRange) {
- this.dateRange = [formatDate(startOfMonth), formatDate(end)]
- this.params.startDate = this.dateRange[0]
- this.params.endDate = this.dateRange[1]
- }
-
- function formatDate(date) {
- const y = date.getFullYear()
- const m = String(date.getMonth() + 1).padStart(2, '0')
- const d = String(date.getDate()).padStart(2, '0')
- return `${y}-${m}-${d}`
- }
- },
- handleDateChange(value) {
- this.params.startDate = value[0]
- this.params.endDate = value[1]
- this.fetchData()
- },
- // 获取数据
- async fetchData() {
- try {
- this.loading = true
-
- const response = await getAnalysisAllFilterOptions(this.params)
- this.filterOptions = response
-
- const bindResponse = await getAnalysisProductAnalysis(this.params)
- this.filterBindOptions = bindResponse
-
- // 初始化筛选项状态
- this.initializeFilterStates()
- } catch (error) {
- console.error('获取数据失败:', error)
- this.$message.error('获取数据失败')
- } finally {
- this.loading = false
- }
- },
-
- /**
- * 获取月度数据
- * 根据当前筛选条件获取月度分析数据
- */
- async getData() {
- try {
- // 构建筛选参数,转换为接口需要的重复参数名格式
- const filterParams = {}
- const categorySpecs = {}
-
- Object.keys(this.selectedValues).forEach((key) => {
- const value = this.selectedValues[key]
- if (
- value !== null &&
- value !== undefined &&
- Array.isArray(value) &&
- value.length > 0
- ) {
- // 检查是否是规格相关的筛选条件
- if (this.specsData && this.specsData[key]) {
- // 这是规格筛选条件,添加到categorySpecs中
- categorySpecs[key] = value
- } else {
- // 转换键名
- let newKey = '';
- if(key === 'product_code') {
- newKey = 'productCode';
- } else if(key === 'shop_name') {
- newKey = 'shopName';
- } else if(key === 'brand') {
- newKey = 'brand';
- } else if(key === 'customer_name') {
- newKey = 'customerName';
- } else {
- newKey = key;
- }
-
- // 将数组转换为重复参数名格式
- filterParams[newKey] = value
- }
- }
- })
-
- // 如果有规格筛选条件,添加到filterParams中
- if (Object.keys(categorySpecs).length > 0) {
- filterParams.categorySpecs = categorySpecs
- }
-
- console.log('筛选参数:', filterParams)
- console.log('规格筛选条件:', categorySpecs)
-
- const response = await getAnalysisMonthlyAnalysis({
- ...this.params,
- ...filterParams
- })
- this.monthData = response
-
- const resData = await getAnalysisComprehensiveAnalysis({
- ...this.params,
- ...filterParams
- })
-
- // 处理规格数据
- if (resData.category_specs) {
- this.specsData = resData.category_specs
- console.log('规格数据:', this.specsData)
- delete resData.category_specs
- } else {
- this.specsData = {}
- console.log('未找到规格数据')
- }
-
- this.moreData = resData
-
- // 渲染图表
- this.renderAllCharts()
-
- // if (resData.length) {
- // this.productData = resData[1].product_code
- // this.shopData = resData[0].shop_name
- // } else {
- // this.productData = []
- // this.shopData = []
- // }
- } catch (error) {
- console.error(error)
- }
- },
-
- /**
- * 初始化筛选项状态
- * 为每个筛选项设置默认的多选模式和空数组
- */
- initializeFilterStates() {
- const values = {}
-
- Object.keys(this.filterOptions).forEach((key) => {
- values[key] = [] // 默认无选中值,使用空数组
- })
-
- this.selectedValues = values
- },
-
- /**
- * 重置筛选项选择
- * @param {string} filterKey - 筛选项的键名
- */
- resetFilter(filterKey) {
- this.selectedValues[filterKey] = []
- },
-
- /**
- * 处理多选值变化
- * @param {string} filterKey - 筛选项的键名
- * @param {Array} values - 选中的值数组
- */
- handleMultipleSelect(filterKey, values) {
- this.selectedValues[filterKey] = values
- this.onFilterChange()
- },
-
- /**
- * 筛选条件变化时的回调
- */
- onFilterChange() {
- console.log('筛选条件变化:', this.selectedValues)
- // 调用月度数据获取
- this.getData()
- },
-
- /**
- * 检查某个选项是否可用
- * @param {string} filterKey - 筛选项的键名
- * @param {string} option - 要检查的选项值
- * @param {Object} currentSelections - 当前所有筛选项的选择状态
- * @returns {boolean} 是否可用
- */
- isOptionAvailable(filterKey, option, currentSelections) {
- // 如果没有绑定数据或不是数组,所有选项都可用
- if (
- !this.filterBindOptions ||
- !Array.isArray(this.filterBindOptions) ||
- this.filterBindOptions.length === 0
- ) {
- return true
- }
-
- // 构建临时选择状态来测试这个选项
- const testSelections = { ...currentSelections }
- testSelections[filterKey] = [option]
-
- // 检查是否有任何绑定记录匹配这个选择组合
- return this.filterBindOptions.some((record) => {
- return this.isSelectionMatch(record, testSelections)
- })
- },
-
- /**
- * 检查选择状态是否匹配绑定记录
- * @param {Object} record - 绑定记录
- * @param {Object} selections - 选择状态
- * @returns {boolean} 是否匹配
- */
- isSelectionMatch(record, selections) {
- // 检查所有筛选项是否都匹配
- return Object.keys(selections).every((filterKey) => {
- const selectedValues = selections[filterKey]
- const recordValues = record[filterKey]
-
- // 如果记录中没有这个筛选项,跳过检查
- if (!recordValues) {
- return true
- }
-
- // 检查是否有交集
- return selectedValues.some((selected) =>
- recordValues.includes(selected)
- )
- })
- },
-
- /**
- * 检查选项是否被禁用
- * @param {string} filterKey - 筛选项的键名
- * @param {string} option - 选项值
- * @returns {boolean} 是否被禁用
- */
- isOptionDisabled(filterKey, option) {
- return !this.availableOptions[filterKey]?.includes(option)
- },
-
- /**
- * 渲染月度数据柱形图
- */
- renderMonthSalesChart() {
- this.$nextTick(() => {
- const chartDom = document.getElementById('monthSalesBarChart')
- if (!chartDom) return
-
- const myChart = echarts.init(chartDom)
- const option = {
- backgroundColor: 'transparent',
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- },
- formatter: function(params) {
- let result = params[0].name + '<br/>'
- params.forEach(param => {
- if (param.seriesName === '販売数量') {
- result += param.seriesName + ': ' + param.value + '<br/>'
- } else {
- result += param.seriesName + ': ¥' + param.value.toLocaleString('ja-JP')
- }
- })
- return result
- }
- },
- legend: {
- data: ['販売数量', '販売金額'],
- textStyle: {
- color: '#333'
- }
- },
- xAxis: {
- type: 'category',
- data: this.monthData.map(item => item.month),
- axisLabel: {
- color: '#666'
- }
- },
- yAxis: [
- {
- type: 'value',
- name: '販売数量',
- position: 'left',
- axisLabel: {
- color: '#666'
- }
- },
- {
- type: 'value',
- name: '販売金額',
- position: 'right',
- axisLabel: {
- color: '#666',
- formatter: function(value) {
- return '¥' + (value / 10000).toFixed(0) + '万'
- }
- }
- }
- ],
- series: [
- {
- name: '販売数量',
- type: 'bar',
- data: this.monthData.map(item => item.totalQuantity),
- itemStyle: {
- color: '#5470c6'
- }
- },
- {
- name: '販売金額',
- type: 'bar',
- yAxisIndex: 1,
- data: this.monthData.map(item => item.totalAmount),
- itemStyle: {
- color: '#91cc75'
- }
- }
- ]
- }
- myChart.setOption(option)
- window.addEventListener('resize', () => myChart.resize())
- })
- },
-
- /**
- * 渲染客户销量占比饼图
- */
- renderCustomerPieChart() {
- this.$nextTick(() => {
- const chartDom = document.getElementById('customerPieChart')
- if (!chartDom) return
-
- const myChart = echarts.init(chartDom)
- const data = this.moreData.customer_name || []
- const option = {
- backgroundColor: 'transparent',
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b}: {c} ({d}%)'
- },
- legend: {
- orient: 'vertical',
- left: 'left',
- textStyle: {
- color: '#333'
- }
- },
- series: [
- {
- name: '客户销量占比',
- type: 'pie',
- radius: '50%',
- data: data.map(item => ({
- value: item.totalQuantity,
- name: item.customer_name
- })),
- emphasis: {
- itemStyle: {
- shadowBlur: 10,
- shadowOffsetX: 0,
- shadowColor: 'rgba(0, 0, 0, 0.5)'
- }
- }
- }
- ]
- }
- myChart.setOption(option)
- window.addEventListener('resize', () => myChart.resize())
- })
- },
-
- /**
- * 渲染商品销量排行柱形图
- */
- renderProductRankingChart() {
- this.$nextTick(() => {
- const chartDom = document.getElementById('productRankingChart')
- if (!chartDom) return
-
- const myChart = echarts.init(chartDom)
- const data = this.moreData.product_code || []
- const option = {
- backgroundColor: 'transparent',
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- }
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'value',
- axisLabel: {
- color: '#666'
- }
- },
- yAxis: {
- type: 'category',
- data: data.map(item => item.product_code),
- axisLabel: {
- color: '#666'
- }
- },
- series: [
- {
- name: '販売数量',
- type: 'bar',
- data: data.map(item => item.totalQuantity),
- itemStyle: {
- color: '#5470c6'
- }
- }
- ]
- }
- myChart.setOption(option)
- window.addEventListener('resize', () => myChart.resize())
- })
- },
-
- /**
- * 渲染品牌销量占比饼图
- */
- renderBrandPieChart() {
- this.$nextTick(() => {
- const chartDom = document.getElementById('brandPieChart')
- if (!chartDom) return
-
- const myChart = echarts.init(chartDom)
- const data = this.moreData.brand || []
- const option = {
- backgroundColor: 'transparent',
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b}: {c} ({d}%)'
- },
- legend: {
- orient: 'vertical',
- left: 'left',
- textStyle: {
- color: '#333'
- }
- },
- series: [
- {
- name: '品牌销量占比',
- type: 'pie',
- radius: '50%',
- data: data.map(item => ({
- value: item.totalQuantity,
- name: item.brand
- })),
- emphasis: {
- itemStyle: {
- shadowBlur: 10,
- shadowOffsetX: 0,
- shadowColor: 'rgba(0, 0, 0, 0.5)'
- }
- }
- }
- ]
- }
- myChart.setOption(option)
- window.addEventListener('resize', () => myChart.resize())
- })
- },
-
- /**
- * 渲染规格销售占比饼图
- */
- renderSpecsPieChart(specKey, chartId) {
- this.$nextTick(() => {
- const chartDom = document.getElementById(chartId)
- if (!chartDom) {
- console.warn(`图表容器未找到: ${chartId}`)
- return
- }
-
- const myChart = echarts.init(chartDom)
- const data = this.formatSpecsData(this.specsData[specKey])
-
- console.log(`渲染规格图表: ${specKey}`, {
- chartId,
- data,
- specsData: this.specsData[specKey]
- })
-
- if (!data || data.length === 0) {
- console.warn(`规格数据为空: ${specKey}`)
- return
- }
-
- const option = {
- backgroundColor: 'transparent',
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b}: {c} ({d}%)'
- },
- legend: {
- orient: 'vertical',
- left: 'left',
- textStyle: {
- color: '#333'
- }
- },
- series: [
- {
- name: specKey + '销售占比',
- type: 'pie',
- radius: '50%',
- data: data.map(item => ({
- value: item.totalQuantity,
- name: item.name
- })),
- emphasis: {
- itemStyle: {
- shadowBlur: 10,
- shadowOffsetX: 0,
- shadowColor: 'rgba(0, 0, 0, 0.5)'
- }
- }
- }
- ]
- }
- myChart.setOption(option)
- window.addEventListener('resize', () => myChart.resize())
- })
- },
-
- /**
- * 获取图表ID
- * @param {string} key - 数据键名
- * @returns {string} 图表ID
- */
- getChartId(key) {
- if (key === 'customer_name') {
- return 'customerPieChart'
- } else if (key === 'product_code') {
- return 'productRankingChart'
- } else if (key === 'brand') {
- return 'brandPieChart'
- }
- return 'defaultChart'
- },
-
- /**
- * 获取规格图表ID
- * @param {string} key - 规格键名
- * @param {number} index - 索引
- * @returns {string} 图表ID
- */
- getSpecsChartId(key, index) {
- return `specsPieChart${index}`
- },
-
- /**
- * 渲染所有图表
- */
- renderAllCharts() {
- // 先渲染基础图表
- this.renderMonthSalesChart()
- this.renderCustomerPieChart()
- this.renderProductRankingChart()
- this.renderBrandPieChart()
-
- // 规格图表由watch监听器处理
- }
- }
- }
- </script>
-
- <style lang="scss" scoped>
- .app-container {
- display: flex;
- flex-direction: column;
- gap: 10px;
- padding: 10px;
- width: 100%;
- min-height: 100vh;
- box-sizing: border-box;
- }
- .filter-card {
- background-color: #fff;
- padding: 10px 20px;
- border-radius: 8px;
- border: 1px solid #e8e8e8;
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 20px;
- }
- .filter-section {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
-
- .filter-cell {
- background-color: #fff;
- border-radius: 6px;
- border: 1px solid #e8e8e8;
- overflow: hidden;
- display: flex;
- }
-
- .cell-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 8px 12px;
- background-color: #f8f9fa;
- width: 150px;
- }
-
- .cell-content {
- padding: 8px 12px;
- flex-grow: 1;
- width: 0;
- display: flex;
- align-items: center;
- }
-
- .filter-title {
- font-weight: 500;
- color: #333;
- font-size: 14px;
- }
-
- .header-buttons {
- display: flex;
- gap: 8px;
- }
-
- .header-buttons .el-button {
- padding: 4px 8px;
- font-size: 12px;
- color: #666;
- }
-
- .header-buttons .el-button:hover {
- color: #409eff;
- }
-
- .header-buttons .el-button.active-mode {
- color: #409eff;
- font-weight: 500;
- }
-
- .checkbox-group {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- gap: 8px 12px;
- max-height: 120px;
- overflow-y: auto;
- }
-
- .checkbox-group .el-checkbox {
- margin-right: 0;
- margin-bottom: 0;
- white-space: nowrap;
- flex-shrink: 0;
- }
-
- .checkbox-group .el-checkbox__label {
- font-size: 13px;
- color: #666;
- }
-
- /* 可用选项的高亮样式 */
- .available-option .el-checkbox__label {
- color: #333;
- font-weight: 500;
- }
-
- .available-option:hover .el-checkbox__label {
- color: #409eff;
- }
-
- /* 禁用选项的样式 */
- .disabled-option .el-checkbox__label {
- color: #c0c4cc;
- text-decoration: line-through;
- }
-
- .disabled-option .el-checkbox__input.is-disabled .el-checkbox__inner {
- background-color: #f5f7fa;
- border-color: #e4e7ed;
- }
-
- /* 选中状态的样式 */
- .available-option.is-checked .el-checkbox__label {
- color: #409eff;
- font-weight: 600;
- }
- .filter-row {
- display: flex;
- align-items: center;
- gap: 20px;
- flex-wrap: wrap;
- }
- .filter-item {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .filter-item label {
- font-weight: 500;
- color: #333;
- white-space: nowrap;
- }
-
- .charts-section {
- width: 100%;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
-
- .chart-card {
- background-color: #fff;
- border-radius: 8px;
- overflow: hidden;
- }
- .card-header {
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 18px;
- padding: 15px 20px;
- }
- .chart-container {
- padding: 20px;
- width: 100%;
- height: auto;
- min-height: 0;
- overflow: hidden;
- }
- .data-section {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 10px;
- .table-chart-data {
- .table-chart-data-header {
- text-align: center;
- font-size: 18px;
- font-weight: 500;
- color: #333;
- padding: 10px 0;
- }
- .table-chart-data-content {
- display: grid;
- gap: 10px;
- background-color: #fff;
- border-radius: 8px;
- border: 1px solid #e8e8e8;
- .table-card {
- width: 100%;
- height: 100%;
- }
- .chart-card {
- width: 100%;
- height: 100%;
- min-height: 300px;
- }
- }
- }
- }
-
- .no-data-section {
- display: flex;
- justify-content: center;
- align-items: center;
- min-height: 400px;
- flex-direction: column;
- }
- .no-data-content {
- text-align: center;
- color: #999;
- }
- .no-data-content i {
- font-size: 48px;
- margin-bottom: 20px;
- color: #ccc;
- display: block;
- }
- .no-data-content p {
- font-size: 16px;
- margin: 0 0 10px 0;
- color: #666;
- }
- .no-data-content small {
- font-size: 14px;
- color: #aaa;
- }
- </style>
|