feat(交易运营): 新增交易运营模块相关功能
新增交易运营模块的统计、分析、分销和订单管理功能 - 添加主播维度统计和店铺维度统计页面 - 实现店铺评分监控、地域分布分析和商品数据统计功能 - 完成分销效果核算和分销订单查询功能 - 开发订单管理页面及相关接口 - 修复知识库文档列表和详情的部分问题 - 更新环境配置和API接口文件
This commit is contained in:
269
src/views/trade/operation/analysis/product/index.vue
Normal file
269
src/views/trade/operation/analysis/product/index.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div class="trade-operation-analysis-product">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>商品数据统计</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 商品类目分布 -->
|
||||
<div class="chart-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">商品类目分布</div>
|
||||
</template>
|
||||
<div ref="categoryChartRef" class="chart"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 搜索条件 -->
|
||||
<div class="search-container">
|
||||
<el-form :model="searchParams" :inline="true" class="search-form">
|
||||
<el-form-item label="时间范围">
|
||||
<el-date-picker
|
||||
v-model="searchParams.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品类目">
|
||||
<el-select v-model="searchParams.categoryId" placeholder="选择商品类目">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option v-for="category in categories" :key="category.id" :label="category.name" :value="category.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- SKU销售排行 -->
|
||||
<div class="sku-ranking">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">SKU销售排行</div>
|
||||
</template>
|
||||
<el-table :data="skuList" style="width: 100%">
|
||||
<el-table-column prop="rank" label="排名" width="80" />
|
||||
<el-table-column prop="productName" label="商品名称" />
|
||||
<el-table-column prop="sku" label="SKU" />
|
||||
<el-table-column prop="category" label="类目" />
|
||||
<el-table-column prop="sales" label="销售额" />
|
||||
<el-table-column prop="salesVolume" label="销量" />
|
||||
<el-table-column prop="status" label="状态">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === 'hot' ? 'success' : 'warning'" size="small">{{
|
||||
scope.row.status === 'hot' ? '热销' : '滞销'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const searchParams = reactive({
|
||||
dateRange: [],
|
||||
categoryId: '',
|
||||
});
|
||||
|
||||
interface Category {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface SkuItem {
|
||||
rank: number;
|
||||
productName: string;
|
||||
sku: string;
|
||||
category: string;
|
||||
sales: number;
|
||||
salesVolume: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const categories = ref<Category[]>([]);
|
||||
const skuList = ref<SkuItem[]>([]);
|
||||
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const categoryChartRef = ref();
|
||||
let categoryChart: echarts.ECharts | null = null;
|
||||
|
||||
// 模拟商品类目数据
|
||||
const getMockCategories = () => {
|
||||
return [
|
||||
{ id: 1, name: '电子产品' },
|
||||
{ id: 2, name: '服装鞋帽' },
|
||||
{ id: 3, name: '食品饮料' },
|
||||
{ id: 4, name: '家居用品' },
|
||||
{ id: 5, name: '美妆护肤' },
|
||||
];
|
||||
};
|
||||
|
||||
// 模拟SKU销售排行数据
|
||||
const getMockSkuList = () => {
|
||||
const skus: SkuItem[] = [
|
||||
{ rank: 1, productName: '智能手机', sku: 'SKU001', category: '电子产品', sales: 890000, salesVolume: 2500, status: 'hot' },
|
||||
{ rank: 2, productName: '运动跑鞋', sku: 'SKU002', category: '服装鞋帽', sales: 650000, salesVolume: 3200, status: 'hot' },
|
||||
{ rank: 3, productName: '高端耳机', sku: 'SKU003', category: '电子产品', sales: 420000, salesVolume: 1800, status: 'hot' },
|
||||
{ rank: 4, productName: '有机蔬菜', sku: 'SKU004', category: '食品饮料', sales: 380000, salesVolume: 5000, status: 'hot' },
|
||||
{ rank: 5, productName: '护肤套装', sku: 'SKU005', category: '美妆护肤', sales: 320000, salesVolume: 1200, status: 'hot' },
|
||||
{ rank: 6, productName: '办公椅', sku: 'SKU006', category: '家居用品', sales: 280000, salesVolume: 800, status: 'hot' },
|
||||
{ rank: 7, productName: 'T恤衫', sku: 'SKU007', category: '服装鞋帽', sales: 250000, salesVolume: 4500, status: 'hot' },
|
||||
{ rank: 8, productName: '厨房电器', sku: 'SKU008', category: '家居用品', sales: 180000, salesVolume: 600, status: 'warning' },
|
||||
{ rank: 9, productName: '零食礼包', sku: 'SKU009', category: '食品饮料', sales: 150000, salesVolume: 3000, status: 'warning' },
|
||||
{ rank: 10, productName: '化妆品', sku: 'SKU010', category: '美妆护肤', sales: 120000, salesVolume: 900, status: 'warning' },
|
||||
];
|
||||
return skus;
|
||||
};
|
||||
|
||||
// 模拟类目分布数据
|
||||
const getMockCategoryDistribution = () => {
|
||||
return [
|
||||
{ categoryName: '电子产品', sales: 1310000 },
|
||||
{ categoryName: '服装鞋帽', sales: 900000 },
|
||||
{ categoryName: '食品饮料', sales: 530000 },
|
||||
{ categoryName: '家居用品', sales: 460000 },
|
||||
{ categoryName: '美妆护肤', sales: 440000 },
|
||||
];
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
// 使用模拟数据
|
||||
categories.value = getMockCategories();
|
||||
skuList.value = getMockSkuList();
|
||||
pagination.total = 50;
|
||||
initCategoryChart(getMockCategoryDistribution());
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
searchParams.dateRange = [];
|
||||
searchParams.categoryId = '';
|
||||
pagination.currentPage = 1;
|
||||
pagination.pageSize = 10;
|
||||
};
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.pageSize = size;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
const handleCurrentChange = (current: number) => {
|
||||
pagination.currentPage = current;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
const initCategoryChart = (categoryDistribution: any[]) => {
|
||||
if (!categoryChartRef.value) return;
|
||||
|
||||
if (categoryChart) {
|
||||
categoryChart.dispose();
|
||||
}
|
||||
|
||||
categoryChart = echarts.init(categoryChartRef.value);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
data: categoryDistribution.map((item) => item.categoryName),
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '商品类目',
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
data: categoryDistribution.map((item) => ({
|
||||
value: item.sales,
|
||||
name: item.categoryName,
|
||||
})),
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
categoryChart.setOption(option);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
categoryChart?.resize();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trade-operation-analysis-product {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.sku-ranking {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
238
src/views/trade/operation/analysis/region/index.vue
Normal file
238
src/views/trade/operation/analysis/region/index.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="trade-operation-analysis-region">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>地域分布分析</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 地域分布图表 -->
|
||||
<div class="chart-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">地域销售分布</div>
|
||||
</template>
|
||||
<div ref="regionChartRef" class="chart"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 搜索条件 -->
|
||||
<div class="search-container">
|
||||
<el-form :model="searchParams" :inline="true" class="search-form">
|
||||
<el-form-item label="时间范围">
|
||||
<el-date-picker
|
||||
v-model="searchParams.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="区域级别">
|
||||
<el-select v-model="searchParams.regionLevel" placeholder="选择区域级别">
|
||||
<el-option label="省份" value="province" />
|
||||
<el-option label="城市" value="city" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- 地域销售排行 -->
|
||||
<div class="region-ranking">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">地域销售排行</div>
|
||||
</template>
|
||||
<el-table :data="regionList" style="width: 100%">
|
||||
<el-table-column prop="rank" label="排名" width="80" />
|
||||
<el-table-column prop="regionName" label="地区名称" />
|
||||
<el-table-column prop="sales" label="销售额" />
|
||||
<el-table-column prop="orderCount" label="订单数" />
|
||||
<el-table-column prop="avgOrderValue" label="客单价" />
|
||||
<el-table-column prop="poiCount" label="店铺数量" />
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const searchParams = reactive({
|
||||
dateRange: [],
|
||||
regionLevel: 'province',
|
||||
});
|
||||
|
||||
const regionList = ref([]);
|
||||
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const regionChartRef = ref();
|
||||
let regionChart: echarts.ECharts | null = null;
|
||||
|
||||
// 模拟地域分布数据
|
||||
const getMockRegionDistribution = () => {
|
||||
const regions = [
|
||||
{ regionName: '北京', sales: 1200000 },
|
||||
{ regionName: '上海', sales: 1500000 },
|
||||
{ regionName: '广州', sales: 980000 },
|
||||
{ regionName: '深圳', sales: 1100000 },
|
||||
{ regionName: '杭州', sales: 850000 },
|
||||
{ regionName: '成都', sales: 780000 },
|
||||
{ regionName: '武汉', sales: 650000 },
|
||||
{ regionName: '西安', sales: 520000 },
|
||||
];
|
||||
return regions;
|
||||
};
|
||||
|
||||
// 模拟地域销售排行数据
|
||||
const getMockRegionList = () => {
|
||||
const regions = [
|
||||
{ rank: 1, regionName: '上海', sales: 1500000, orderCount: 3200, avgOrderValue: 468.75, poiCount: 25 },
|
||||
{ rank: 2, regionName: '北京', sales: 1200000, orderCount: 2800, avgOrderValue: 428.57, poiCount: 20 },
|
||||
{ rank: 3, regionName: '深圳', sales: 1100000, orderCount: 2600, avgOrderValue: 423.08, poiCount: 18 },
|
||||
{ rank: 4, regionName: '广州', sales: 980000, orderCount: 2300, avgOrderValue: 426.09, poiCount: 15 },
|
||||
{ rank: 5, regionName: '杭州', sales: 850000, orderCount: 2100, avgOrderValue: 404.76, poiCount: 12 },
|
||||
{ rank: 6, regionName: '成都', sales: 780000, orderCount: 1900, avgOrderValue: 410.53, poiCount: 10 },
|
||||
{ rank: 7, regionName: '武汉', sales: 650000, orderCount: 1600, avgOrderValue: 406.25, poiCount: 8 },
|
||||
{ rank: 8, regionName: '西安', sales: 520000, orderCount: 1300, avgOrderValue: 400.0, poiCount: 6 },
|
||||
{ rank: 9, regionName: '南京', sales: 480000, orderCount: 1200, avgOrderValue: 400.0, poiCount: 5 },
|
||||
{ rank: 10, regionName: '重庆', sales: 450000, orderCount: 1100, avgOrderValue: 409.09, poiCount: 7 },
|
||||
];
|
||||
return regions;
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
// 使用模拟数据
|
||||
regionList.value = getMockRegionList();
|
||||
pagination.total = 50;
|
||||
initRegionChart(getMockRegionDistribution());
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
searchParams.dateRange = [];
|
||||
searchParams.regionLevel = 'province';
|
||||
pagination.currentPage = 1;
|
||||
pagination.pageSize = 10;
|
||||
};
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.pageSize = size;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
const handleCurrentChange = (current: number) => {
|
||||
pagination.currentPage = current;
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
const initRegionChart = (regionDistribution: any[]) => {
|
||||
if (!regionChartRef.value) return;
|
||||
|
||||
if (regionChart) {
|
||||
regionChart.dispose();
|
||||
}
|
||||
|
||||
regionChart = echarts.init(regionChartRef.value);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
data: regionDistribution.map((item) => item.regionName),
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '地域销售',
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
data: regionDistribution.map((item) => ({
|
||||
value: item.sales,
|
||||
name: item.regionName,
|
||||
})),
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
regionChart.setOption(option);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
regionChart?.resize();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trade-operation-analysis-region {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.region-ranking {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
269
src/views/trade/operation/analysis/shop/index.vue
Normal file
269
src/views/trade/operation/analysis/shop/index.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div class="trade-operation-analysis-shop">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>店铺评分监控</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 评分趋势 -->
|
||||
<div class="chart-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">评分趋势</div>
|
||||
</template>
|
||||
<div ref="scoreChartRef" class="chart"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 搜索条件 -->
|
||||
<div class="search-container">
|
||||
<el-form :model="searchParams" :inline="true" class="search-form">
|
||||
<el-form-item label="时间范围">
|
||||
<el-date-picker
|
||||
v-model="searchParams.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间粒度">
|
||||
<el-select v-model="searchParams.granularity" placeholder="选择时间粒度">
|
||||
<el-option label="日" value="day" />
|
||||
<el-option label="周" value="week" />
|
||||
<el-option label="月" value="month" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- 评分设置 -->
|
||||
<div class="score-setting">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">评分预警设置</div>
|
||||
</template>
|
||||
<el-form :model="scoreSettings" label-width="120px">
|
||||
<el-form-item label="口碑分预警阈值">
|
||||
<el-input-number v-model="scoreSettings.reputationThreshold" :min="0" :max="5" :step="0.1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="体验分预警阈值">
|
||||
<el-input-number v-model="scoreSettings.experienceThreshold" :min="0" :max="5" :step="0.1" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSaveSettings">保存设置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 评分详情 -->
|
||||
<div class="score-detail">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">评分详情</div>
|
||||
</template>
|
||||
<el-table :data="scoreDetail" style="width: 100%">
|
||||
<el-table-column prop="date" label="日期" />
|
||||
<el-table-column prop="reputationScore" label="口碑分">
|
||||
<template #default="scope">
|
||||
<div class="score-item" :class="{ warning: scope.row.reputationScore < scoreSettings.reputationThreshold }">
|
||||
{{ scope.row.reputationScore }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="experienceScore" label="体验分">
|
||||
<template #default="scope">
|
||||
<div class="score-item" :class="{ warning: scope.row.experienceScore < scoreSettings.experienceThreshold }">
|
||||
{{ scope.row.experienceScore }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="commentCount" label="评价数量" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const searchParams = reactive({
|
||||
dateRange: [],
|
||||
granularity: 'day',
|
||||
});
|
||||
|
||||
const scoreSettings = reactive({
|
||||
reputationThreshold: 4.0,
|
||||
experienceThreshold: 4.2,
|
||||
});
|
||||
|
||||
interface ScoreDetail {
|
||||
date: string;
|
||||
reputationScore: string;
|
||||
experienceScore: string;
|
||||
commentCount: number;
|
||||
}
|
||||
|
||||
const scoreDetail = ref<ScoreDetail[]>([]);
|
||||
|
||||
const scoreChartRef = ref();
|
||||
let scoreChart: echarts.ECharts | null = null;
|
||||
|
||||
// 模拟评分趋势数据
|
||||
const getMockScoreTrend = () => {
|
||||
const dates = ['1月', '2月', '3月', '4月', '5月', '6月'];
|
||||
return dates.map((date) => ({
|
||||
date,
|
||||
reputationScore: 3.8 + Math.random() * 0.7,
|
||||
experienceScore: 4.0 + Math.random() * 0.5,
|
||||
}));
|
||||
};
|
||||
|
||||
// 模拟评分详情数据
|
||||
const getMockScoreDetail = () => {
|
||||
const dates = ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04', '2024-01-05'];
|
||||
return dates.map((date) => ({
|
||||
date,
|
||||
reputationScore: (3.8 + Math.random() * 0.7).toFixed(1),
|
||||
experienceScore: (4.0 + Math.random() * 0.5).toFixed(1),
|
||||
commentCount: 100 + Math.floor(Math.random() * 200),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
// 使用模拟数据
|
||||
scoreDetail.value = getMockScoreDetail();
|
||||
initScoreChart(getMockScoreTrend());
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
searchParams.dateRange = [];
|
||||
searchParams.granularity = 'day';
|
||||
};
|
||||
|
||||
const handleSaveSettings = () => {
|
||||
// 保存评分预警设置
|
||||
// 这里可以调用API保存设置
|
||||
};
|
||||
|
||||
const initScoreChart = (scoreTrend: any[]) => {
|
||||
if (!scoreChartRef.value) return;
|
||||
|
||||
if (scoreChart) {
|
||||
scoreChart.dispose();
|
||||
}
|
||||
|
||||
scoreChart = echarts.init(scoreChartRef.value);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985',
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
data: ['口碑分', '体验分'],
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: scoreTrend.map((item) => item.date),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '评分',
|
||||
min: 0,
|
||||
max: 5,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '口碑分',
|
||||
type: 'line',
|
||||
data: scoreTrend.map((item) => item.reputationScore),
|
||||
smooth: true,
|
||||
},
|
||||
{
|
||||
name: '体验分',
|
||||
type: 'line',
|
||||
data: scoreTrend.map((item) => item.experienceScore),
|
||||
smooth: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
scoreChart.setOption(option);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
scoreChart?.resize();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trade-operation-analysis-shop {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.score-setting {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.score-detail {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.score-item {
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.score-item.warning {
|
||||
background-color: #fde2e2;
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user