123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807 |
- <template>
- <div class="overall-analysis-page">
- <!-- 筛选 -->
- <div class="filter-card">
- <div class="filter-section">
- <div class="filter-row">
- <div class="filter-item" v-if="false">
- <label>店铺:</label>
- <el-select
- v-model="filters.shop"
- placeholder="请选择店铺(默认全部)"
- clearable
- filterable
- style="width: 180px"
- size="small"
- >
- <el-option label="全部店铺" value="" />
- <el-option
- v-for="shop in filterOptions.shops"
- :key="shop"
- :label="shop"
- :value="shop"
- />
- </el-select>
- </div>
-
-
-
- <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"
- @change="handleDateChange"
- style="width: 380px"
- size="small"
- />
- </div>
-
- <div class="filter-item">
- <el-button type="primary" @click="fetchData" :loading="loading" size="small">
- 查询分析
- </el-button>
- <el-button @click="resetFilter" size="small">重置筛选</el-button>
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- 图表区域 -->
- <div class="charts-section">
- <el-row :gutter="20">
- <el-col :span="12">
- <!-- 各店铺销售金额占比 -->
- <div class="chart-card">
- <div class="card-header">
- <span>各店铺销售金额占比</span>
- </div>
- <div class="chart-container">
- <div
- id="shopAmountChart"
- style="width: 100%; height: 400px"
- ></div>
- </div>
- </div>
- </el-col>
- <el-col :span="12">
- <!-- 各店铺销售数量占比 -->
- <div class="chart-card">
- <div class="card-header">
- <span>各店铺销售数量占比</span>
- </div>
- <div class="chart-container">
- <div
- id="shopQuantityChart"
- style="width: 100%; height: 400px"
- ></div>
- </div>
- </div>
- </el-col>
- </el-row>
-
- <!-- 各品类销售趋势 -->
- <div class="chart-card">
- <div class="card-header">
- <span>各品类销售数量和销售额趋势</span>
- </div>
- <div class="chart-container">
- <div id="categoryTrendChart" style="width: 100%; height: 500px"></div>
- </div>
- </div>
-
-
-
- </div>
- </div>
- </template>
-
- <script>
- import * as echarts from "echarts";
- import { getReportFilterOptions, getShopAnalysisReport } from '@/api/sales-analysis'
-
- export default {
- name: "ShopAnalysis",
- components: {},
- data() {
- return {
- loading: false,
- dateRange: null,
- filters: {
- shop: "",
- },
- filterOptions: {
- shops: [],
- },
- statsData: {
- basicStats: null,
- shopAmountData: [],
- shopQuantityData: [],
- categoryTrendData: [],
- },
- shopAmountChart: null,
- shopQuantityChart: null,
- categoryTrendChart: null,
- };
- },
- filters: {
- jpMoney(value) {
- if (value === 0) {
- return "0";
- }
- if (value < 10000) {
- return value.toLocaleString("ja-JP", {
- style: "currency",
- currency: "JPY",
- minimumFractionDigits: 0,
- maximumFractionDigits: 0,
- });
- } else {
- // 将value转换为万日元
- const jpValue = (value / 10000).toFixed(0);
- return jpValue;
- }
- },
- },
- mounted() {
- this.initDateRange();
- this.fetchFilterOptions();
- this.fetchData();
- },
- beforeDestroy() {
- if (this.shopAmountChart) {
- this.shopAmountChart.dispose();
- }
- if (this.shopQuantityChart) {
- this.shopQuantityChart.dispose();
- }
- if (this.categoryTrendChart) {
- this.categoryTrendChart.dispose();
- }
-
- },
- methods: {
- // 初始化日期范围(默认最近30天)
- initDateRange() {
- const endDate = new Date();
- const startDate = new Date();
- startDate.setDate(endDate.getDate() - 30);
-
- this.dateRange = [
- startDate.toISOString().split("T")[0],
- endDate.toISOString().split("T")[0],
- ];
- },
-
- // 日期变化处理
- handleDateChange(dates) {
- this.dateRange = dates;
- },
-
- // 重置筛选
- resetFilter() {
- this.initDateRange();
- this.filters = {
- shop: "",
- };
- this.fetchData();
- },
-
-
-
- // 获取筛选选项
- async fetchFilterOptions() {
- try {
- const response = await getReportFilterOptions();
-
- if (response.code === 200) {
- this.filterOptions = {
- shops: response.data.shops || [],
- };
- } else {
- console.error("获取筛选选项失败:", response.message);
- }
- } catch (error) {
- console.error("获取筛选选项失败:", error);
- }
- },
-
- // 获取数据
- async fetchData() {
- if (!this.dateRange || this.dateRange.length !== 2) {
- this.$message.warning("请选择日期范围");
- return;
- }
-
- try {
- this.loading = true;
-
- // 构建查询参数
- const params = {
- startDate: this.dateRange[0],
- endDate: this.dateRange[1],
- };
-
- // 添加店铺筛选条件(如果选择了特定店铺)
- if (this.filters.shop) {
- params.shop = this.filters.shop;
- }
-
-
-
- const response = await getShopAnalysisReport(params);
-
- if (response.code === 200) {
- this.statsData = response.data;
-
-
-
- this.$nextTick(() => {
- this.renderShopAmountChart();
- this.renderShopQuantityChart();
- this.renderCategoryTrendChart();
- });
- } else {
- this.$message.error(response.message || "获取数据失败");
- }
- } catch (error) {
- console.error("获取数据失败:", error);
- this.$message.error("获取数据失败");
- } finally {
- this.loading = false;
- }
- },
-
- // 渲染店铺销售金额占比图表
- renderShopAmountChart() {
- if (
- !this.statsData.shopAmountData ||
- this.statsData.shopAmountData.length === 0
- ) {
- const chartDom = document.getElementById("shopAmountChart");
- if (chartDom && this.shopAmountChart) {
- this.shopAmountChart.clear();
- }
- return;
- }
-
- const chartDom = document.getElementById("shopAmountChart");
- if (!chartDom) return;
-
- if (this.shopAmountChart) {
- this.shopAmountChart.dispose();
- }
-
- this.shopAmountChart = echarts.init(chartDom);
-
- const data = this.statsData.shopAmountData.map((item) => ({
- name: item.shopName,
- value: item.amount,
- percentage: item.percentage,
- }));
-
- const option = {
- tooltip: {
- trigger: "item",
- formatter: (params) => {
- return `${params.name}<br/>销售额: ¥${this.formatNumber(
- params.value
- )}<br/>占比: ${params.data.percentage}%`;
- },
- },
- legend: {
- type: "scroll",
- orient: "vertical",
- right: 10,
- top: 20,
- bottom: 20,
- data: data.map((item) => item.name),
- textStyle: {
- color: "#666",
- },
- formatter: (name) => {
- const item = data.find((p) => p.name === name);
- const displayName =
- name.length > 15 ? name.slice(0, 15) + "..." : name;
-
- if (item && item.percentage !== undefined) {
- return `${displayName} ${item.percentage}%`;
- }
- return displayName;
- },
- },
- series: [
- {
- name: "销售金额",
- type: "pie",
- radius: ["50%", "70%"],
- center: ["40%", "50%"],
- avoidLabelOverlap: false,
- itemStyle: {
- borderRadius: 10,
- borderColor: "#fff",
- borderWidth: 2,
- },
- label: {
- show: false,
- position: "center",
- },
- emphasis: {
- label: {
- show: true,
- fontSize: "20",
- fontWeight: "bold",
- },
- },
- labelLine: {
- show: false,
- },
- data: data,
- color: [
- "#5470C6",
- "#91CC75",
- "#FAC858",
- "#EE6666",
- "#73C0DE",
- "#3BA272",
- "#FC8452",
- "#9A60B4",
- "#EA7CCC",
- ],
- },
- ],
- };
-
- this.shopAmountChart.setOption(option);
- },
-
- // 渲染店铺销售数量占比图表
- renderShopQuantityChart() {
- if (
- !this.statsData.shopQuantityData ||
- this.statsData.shopQuantityData.length === 0
- ) {
- const chartDom = document.getElementById("shopQuantityChart");
- if (chartDom && this.shopQuantityChart) {
- this.shopQuantityChart.clear();
- }
- return;
- }
-
- const chartDom = document.getElementById("shopQuantityChart");
- if (!chartDom) return;
-
- if (this.shopQuantityChart) {
- this.shopQuantityChart.dispose();
- }
-
- this.shopQuantityChart = echarts.init(chartDom);
-
- const data = this.statsData.shopQuantityData.map((item) => ({
- name: item.shopName,
- value: item.quantity,
- percentage: item.percentage,
- }));
-
- const option = {
- tooltip: {
- trigger: "item",
- formatter: (params) => {
- return `${params.name}<br/>销售数量: ${this.formatNumber(
- params.value
- )}<br/>占比: ${params.data.percentage}%`;
- },
- },
- legend: {
- type: "scroll",
- orient: "vertical",
- right: 10,
- top: 20,
- bottom: 20,
- data: data.map((item) => item.name),
- textStyle: {
- color: "#666",
- },
- formatter: (name) => {
- const item = data.find((p) => p.name === name);
- const displayName =
- name.length > 15 ? name.slice(0, 15) + "..." : name;
-
- if (item && item.percentage !== undefined) {
- return `${displayName} ${item.percentage}%`;
- }
- return displayName;
- },
- },
- series: [
- {
- name: "销售数量",
- type: "pie",
- radius: ["50%", "70%"],
- center: ["40%", "50%"],
- avoidLabelOverlap: false,
- itemStyle: {
- borderRadius: 10,
- borderColor: "#fff",
- borderWidth: 2,
- },
- label: {
- show: false,
- position: "center",
- },
- emphasis: {
- label: {
- show: true,
- fontSize: "20",
- fontWeight: "bold",
- },
- },
- labelLine: {
- show: false,
- },
- data: data,
- color: [
- "#91CC75",
- "#5470C6",
- "#FAC858",
- "#EE6666",
- "#73C0DE",
- "#3BA272",
- "#FC8452",
- "#9A60B4",
- "#EA7CCC",
- ],
- },
- ],
- };
-
- this.shopQuantityChart.setOption(option);
- },
-
- // 渲染品类销售趋势图表
- renderCategoryTrendChart() {
- if (
- !this.statsData.categoryTrendData ||
- this.statsData.categoryTrendData.length === 0
- ) {
- const chartDom = document.getElementById("categoryTrendChart");
- if (chartDom && this.categoryTrendChart) {
- this.categoryTrendChart.clear();
- }
- return;
- }
-
- const chartDom = document.getElementById("categoryTrendChart");
- if (!chartDom) return;
-
- if (this.categoryTrendChart) {
- this.categoryTrendChart.dispose();
- }
-
- this.categoryTrendChart = echarts.init(chartDom);
-
- const categories = this.statsData.categoryTrendData.map(
- (item) => item.category
- );
- const amounts = this.statsData.categoryTrendData.map(
- (item) => item.amount
- );
- const quantities = this.statsData.categoryTrendData.map(
- (item) => item.quantity
- );
-
- const option = {
- tooltip: {
- trigger: "axis",
- axisPointer: {
- type: "cross",
- crossStyle: {
- color: "#999",
- },
- },
- formatter: (params) => {
- let tooltipText = `${params[0].name}<br/>`;
- params.forEach((param) => {
- tooltipText += `${param.marker} ${param.seriesName}: `;
- const value = param.value || 0;
- if (param.seriesName === "销售额") {
- tooltipText += `¥${this.formatNumber(value.toFixed(0))}`;
- } else {
- tooltipText += `${this.formatNumber(value)}`;
- }
- tooltipText += "<br/>";
- });
- return tooltipText;
- },
- },
- legend: {
- data: ["销售额", "销售数量"],
- top: "top",
- itemGap: 20,
- textStyle: {
- color: "#666",
- },
- },
- grid: {
- left: "3%",
- right: "4%",
- bottom: "25%",
- containLabel: true,
- },
- xAxis: [
- {
- type: "category",
- data: categories,
- axisPointer: {
- type: "shadow",
- },
- axisTick: {
- show: false,
- },
- axisLine: {
- show: false,
- },
- axisLabel: {
- color: "#666",
- interval: 0,
- rotate: 45,
- },
- },
- ],
- yAxis: [
- {
- type: "value",
- name: "销售额",
- axisLabel: {
- formatter: "¥{value}",
- color: "#666",
- },
- nameTextStyle: {
- color: "#666",
- padding: [0, 0, 0, 40],
- },
- splitLine: {
- lineStyle: {
- type: "dashed",
- color: "#e0e6f1",
- },
- },
- axisLine: { show: false },
- axisTick: { show: false },
- },
- {
- type: "value",
- name: "销售数量",
- axisLabel: {
- formatter: "{value}",
- color: "#666",
- },
- nameTextStyle: {
- color: "#666",
- padding: [0, 40, 0, 0],
- },
- splitLine: { show: false },
- axisLine: { show: false },
- axisTick: { show: false },
- },
- ],
- series: [
- {
- name: "销售额",
- type: "bar",
- barWidth: "40%",
- data: amounts,
- itemStyle: {
- borderRadius: [5, 5, 0, 0],
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: "#5470C6" },
- { offset: 1, color: "#80A4F3" },
- ]),
- },
- },
- {
- name: "销售数量",
- type: "line",
- yAxisIndex: 1,
- smooth: true,
- data: quantities,
- symbol: "circle",
- symbolSize: 8,
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- {
- offset: 0,
- color: "rgba(91, 204, 189, 0.5)",
- },
- {
- offset: 1,
- color: "rgba(91, 204, 189, 0)",
- },
- ]),
- },
- itemStyle: {
- color: "#5BCCBD",
- },
- },
- ],
- dataZoom: [
- {
- type: "inside",
- start: 0,
- end: 100,
- },
- {
- start: 0,
- end: 100,
- height: 20,
- bottom: 5,
- },
- ],
- };
-
- this.categoryTrendChart.setOption(option);
- },
-
-
-
- // 格式化数字
- formatNumber(num) {
- if (!num) return "0";
- return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
- },
-
-
- },
- };
- </script>
-
- <style scoped>
- .overall-analysis-page {
- height: 100vh;
- overflow-y: auto;
- margin: -20px;
- padding: 10px;
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .filter-card {
- background-color: #fff;
- padding: 20px;
- border-radius: 8px;
- border: 1px solid #e8e8e8;
- }
- .filter-section {
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
-
- .filter-row {
- display: flex;
- align-items: center;
- gap: 10px;
- }
-
- .filter-item {
- display: flex;
- align-items: center;
- gap: 4px;
- }
-
- .filter-item label {
- font-weight: 500;
- color: #333;
- white-space: nowrap;
- width: 70px;
- }
-
- .filter-item .el-select {
- width: 180px;
- }
-
- .filter-item .el-input {
- width: 180px;
- }
-
- .filter-actions {
- display: flex;
- gap: 10px;
- }
-
- .stats-cards {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
- gap: 10px;
- }
-
- .stat-card {
- background: white;
- padding: 20px;
- border-radius: 8px;
- display: flex;
- align-items: center;
- gap: 15px;
- border: 1px solid #e8e8e8;
- }
-
- .stat-icon {
- width: 60px;
- height: 60px;
- border-radius: 50%;
- background: #409eff;
- display: flex;
- align-items: center;
- justify-content: center;
- color: white;
- font-size: 24px;
- flex-shrink: 0;
- }
-
- .stat-content {
- flex: 1;
- }
-
- .stat-title {
- font-size: 16px;
- color: #505050;
- margin-bottom: 5px;
- }
-
- .stat-value {
- font-size: 32px;
- font-weight: 500;
- color: #333;
- margin-bottom: 5px;
- }
-
- .stat-value small {
- font-size: 15px;
- color: #505050;
- }
-
- .stat-desc {
- font-size: 12px;
- color: #999;
- }
-
- .charts-section {
- display: grid;
- grid-template-columns: 1fr;
- gap: 20px;
- }
-
- .chart-card {
- background: white;
- }
-
- .chart-container {
- width: 100%;
- }
-
- .card-header {
- display: flex;
- align-items: center;
- font-weight: 500;
- font-size: 18px;
- padding: 20px;
- justify-content: center;
- }
-
-
-
- .chart-card {
- background-color: #fff;
- border-radius: 8px;
- border: 1px solid #e8e8e8;
- }
- .chart-container {
- padding: 10px 0;
- }
- </style>
|