feat(交易运营): 新增交易运营模块相关功能

新增交易运营模块的统计、分析、分销和订单管理功能
- 添加主播维度统计和店铺维度统计页面
- 实现店铺评分监控、地域分布分析和商品数据统计功能
- 完成分销效果核算和分销订单查询功能
- 开发订单管理页面及相关接口
- 修复知识库文档列表和详情的部分问题
- 更新环境配置和API接口文件
This commit is contained in:
2026-04-07 17:06:56 +08:00
parent 08ae659a56
commit c610c6b327
20 changed files with 2447 additions and 12 deletions

View File

@@ -0,0 +1,311 @@
<template>
<div class="trade-operation-distribution-effect">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>分销效果核算</span>
</div>
</template>
<!-- 搜索条件 -->
<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-input v-model="searchParams.anchorName" placeholder="请输入达人名称" clearable />
</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="chart-container">
<el-card>
<template #header>
<div class="card-header">分销效果趋势</div>
</template>
<div ref="effectChartRef" class="chart"></div>
</el-card>
</div>
<!-- 核心指标 -->
<div class="stats-cards">
<el-card class="stats-card">
<div class="stats-card-title">总销售额</div>
<div class="stats-card-value">{{ statsData.totalSales || 0 }}</div>
</el-card>
<el-card class="stats-card">
<div class="stats-card-title">总佣金金额</div>
<div class="stats-card-value">{{ statsData.totalCommission || 0 }}</div>
</el-card>
<el-card class="stats-card">
<div class="stats-card-title">平均佣金率</div>
<div class="stats-card-value">{{ statsData.avgCommissionRate || 0 }}%</div>
</el-card>
<el-card class="stats-card">
<div class="stats-card-title">达人数量</div>
<div class="stats-card-value">{{ statsData.anchorCount || 0 }}</div>
</el-card>
</div>
<!-- 达人推广效果 -->
<div class="anchor-effect">
<el-card>
<template #header>
<div class="card-header">达人推广效果</div>
</template>
<el-table :data="anchorList" style="width: 100%">
<el-table-column prop="rank" label="排名" width="80" />
<el-table-column prop="anchorName" label="达人名称" />
<el-table-column prop="sales" label="销售额" />
<el-table-column prop="commission" label="佣金金额" />
<el-table-column prop="commissionRate" label="佣金率" />
<el-table-column prop="orderCount" label="订单数" />
<el-table-column prop="conversionRate" 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: [],
anchorName: '',
});
const statsData = reactive({
totalSales: 0,
totalCommission: 0,
avgCommissionRate: 0,
anchorCount: 0,
});
interface AnchorItem {
rank: number;
anchorName: string;
sales: number;
commission: number;
commissionRate: number;
orderCount: number;
conversionRate: number;
}
const anchorList = ref<AnchorItem[]>([]);
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
const effectChartRef = ref();
let effectChart: echarts.ECharts | null = null;
// 模拟分销效果趋势数据
const getMockEffectTrend = () => {
const dates = ['1月', '2月', '3月', '4月', '5月', '6月'];
return dates.map((date) => ({
date,
sales: 500000 + Math.random() * 1000000,
commission: 50000 + Math.random() * 200000,
}));
};
// 模拟达人推广效果数据
const getMockAnchorList = () => {
const anchors: AnchorItem[] = [];
for (let i = 1; i <= 20; i++) {
anchors.push({
rank: i,
anchorName: `达人${i}`,
sales: 100000 + Math.random() * 900000,
commission: 10000 + Math.random() * 180000,
commissionRate: 10 + Math.random() * 10,
orderCount: 100 + Math.floor(Math.random() * 900),
conversionRate: parseFloat((1 + Math.random() * 9).toFixed(2)),
});
}
return anchors;
};
const handleSearch = () => {
// 使用模拟数据
statsData.totalSales = 5000000 + Math.random() * 5000000;
statsData.totalCommission = 500000 + Math.random() * 500000;
statsData.avgCommissionRate = 15 + Math.random() * 5;
statsData.anchorCount = 50 + Math.floor(Math.random() * 50);
anchorList.value = getMockAnchorList();
pagination.total = 20;
initEffectChart(getMockEffectTrend());
};
const handleReset = () => {
searchParams.dateRange = [];
searchParams.anchorName = '';
pagination.currentPage = 1;
pagination.pageSize = 10;
};
const handleSizeChange = (size: number) => {
pagination.pageSize = size;
handleSearch();
};
const handleCurrentChange = (current: number) => {
pagination.currentPage = current;
handleSearch();
};
const initEffectChart = (effectTrend: any[]) => {
if (!effectChartRef.value) return;
if (effectChart) {
effectChart.dispose();
}
effectChart = echarts.init(effectChartRef.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: effectTrend.map((item) => item.date),
},
yAxis: [
{
type: 'value',
name: '金额',
position: 'left',
},
],
series: [
{
name: '销售额',
type: 'line',
data: effectTrend.map((item) => item.sales),
smooth: true,
},
{
name: '佣金金额',
type: 'line',
data: effectTrend.map((item) => item.commission),
smooth: true,
},
],
};
effectChart.setOption(option);
};
onMounted(() => {
handleSearch();
window.addEventListener('resize', () => {
effectChart?.resize();
});
});
</script>
<style scoped>
.trade-operation-distribution-effect {
padding: 20px;
}
.card-header {
font-size: 16px;
font-weight: bold;
}
.search-container {
margin-bottom: 20px;
}
.search-form {
display: flex;
align-items: center;
}
.stats-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 20px;
}
.stats-card {
text-align: center;
padding: 20px;
}
.stats-card-title {
font-size: 14px;
color: #606266;
margin-bottom: 10px;
}
.stats-card-value {
font-size: 24px;
font-weight: bold;
}
.anchor-effect {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.chart-container {
margin: 20px 0;
}
.chart {
width: 100%;
height: 400px;
}
</style>

View File

@@ -0,0 +1,307 @@
<template>
<div class="trade-operation-distribution-order">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>分销订单查询</span>
</div>
</template>
<!-- 搜索条件 -->
<div class="search-container">
<el-form :model="searchParams" :inline="true" class="search-form">
<el-form-item label="订单类型">
<el-select v-model="searchParams.orderType" placeholder="选择订单类型">
<el-option label="全部" value="" />
<el-option label="快分销二创订单" value="quick_distribution" />
<el-option label="分销达人推广订单" value="anchor_promotion" />
</el-select>
</el-form-item>
<el-form-item label="订单号">
<el-input v-model="searchParams.orderNo" placeholder="请输入订单号" clearable />
</el-form-item>
<el-form-item label="达人名称">
<el-input v-model="searchParams.anchorName" placeholder="请输入达人名称" clearable />
</el-form-item>
<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>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 订单列表 -->
<div class="order-list">
<el-table :data="orderList" style="width: 100%">
<el-table-column prop="orderNo" label="订单号" />
<el-table-column prop="orderType" label="订单类型">
<template #default="scope">
<el-tag size="small">
{{ scope.row.orderType === 'quick_distribution' ? '快分销二创' : '达人推广' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="anchorName" label="达人名称" />
<el-table-column prop="productName" label="商品名称" />
<el-table-column prop="amount" label="订单金额" />
<el-table-column prop="commission" label="佣金金额" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作">
<template #default="scope">
<el-button type="primary" size="small" @click="handleOrderDetail(scope.row.id)">查看详情</el-button>
</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>
</div>
<!-- 订单详情对话框 -->
<el-dialog v-model="dialogVisible" title="订单详情" width="80%">
<div v-if="orderDetail" class="order-detail">
<el-descriptions :column="2" border>
<el-descriptions-item label="订单号">{{ orderDetail.orderNo }}</el-descriptions-item>
<el-descriptions-item label="订单类型">{{
orderDetail.orderType === 'quick_distribution' ? '快分销二创' : '达人推广'
}}</el-descriptions-item>
<el-descriptions-item label="达人名称">{{ orderDetail.anchorName }}</el-descriptions-item>
<el-descriptions-item label="订单金额">{{ orderDetail.amount }}</el-descriptions-item>
<el-descriptions-item label="佣金金额">{{ orderDetail.commission }}</el-descriptions-item>
<el-descriptions-item label="佣金比例">{{ orderDetail.commissionRate }}%</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ orderDetail.createTime }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ orderDetail.status }}</el-descriptions-item>
</el-descriptions>
<!-- 商品信息 -->
<div class="detail-section">
<h3>商品信息</h3>
<el-table :data="orderDetail.products" style="width: 100%">
<el-table-column prop="productName" label="商品名称" />
<el-table-column prop="sku" label="SKU" />
<el-table-column prop="quantity" label="数量" />
<el-table-column prop="price" label="单价" />
<el-table-column prop="commission" label="佣金" />
</el-table>
</div>
<!-- 推广信息 -->
<div class="detail-section">
<h3>推广信息</h3>
<el-descriptions :column="2" border>
<el-descriptions-item label="推广渠道">{{ orderDetail.promotion.channel }}</el-descriptions-item>
<el-descriptions-item label="推广时间">{{ orderDetail.promotion.time }}</el-descriptions-item>
<el-descriptions-item label="推广链接">{{ orderDetail.promotion.link }}</el-descriptions-item>
<el-descriptions-item label="推广效果">{{ orderDetail.promotion.effect }}</el-descriptions-item>
</el-descriptions>
</div>
</div>
</el-dialog>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
const searchParams = reactive({
orderType: '',
orderNo: '',
anchorName: '',
dateRange: [],
});
interface OrderItem {
id: number;
orderNo: string;
orderType: string;
anchorName: string;
productName: string;
amount: number;
commission: number;
createTime: string;
}
const orderList = ref<OrderItem[]>([]);
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
interface OrderDetail {
orderNo: string;
orderType: string;
anchorName: string;
amount: number;
commission: number;
commissionRate: number;
createTime: string;
status: string;
products: {
productName: string;
sku: string;
quantity: number;
price: number;
commission: number;
}[];
promotion: {
channel: string;
time: string;
link: string;
effect: string;
};
}
const dialogVisible = ref(false);
const orderDetail = ref<OrderDetail | null>(null);
// 模拟分销订单列表数据
const getMockOrderList = () => {
const orderTypes = ['quick_distribution', 'anchor_promotion'];
const orders: OrderItem[] = [];
for (let i = 1; i <= 20; i++) {
orders.push({
id: i,
orderNo: `DO${Date.now() + i}`,
orderType: orderTypes[Math.floor(Math.random() * orderTypes.length)],
anchorName: `达人${i}`,
productName: `商品${i}`,
amount: 1000 + Math.random() * 9000,
commission: 100 + Math.random() * 900,
createTime: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString(),
});
}
return orders;
};
// 模拟分销订单详情数据
const getMockOrderDetail = (id: number) => {
return {
orderNo: `DO${Date.now() + id}`,
orderType: Math.random() > 0.5 ? 'quick_distribution' : 'anchor_promotion',
anchorName: `达人${id}`,
amount: 5000 + Math.random() * 5000,
commission: 500 + Math.random() * 500,
commissionRate: 10 + Math.random() * 10,
createTime: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
status: '已完成',
products: [
{
productName: '商品1',
sku: 'SKU001',
quantity: 2,
price: 1500,
commission: 300,
},
{
productName: '商品2',
sku: 'SKU002',
quantity: 1,
price: 2500,
commission: 500,
},
],
promotion: {
channel: '抖音',
time: new Date(Date.now() - 11 * 24 * 60 * 60 * 1000).toISOString(),
link: `https://example.com/promotion/${id}`,
effect: '良好',
},
};
};
const handleSearch = () => {
// 使用模拟数据
orderList.value = getMockOrderList();
pagination.total = 20;
};
const handleReset = () => {
searchParams.orderType = '';
searchParams.orderNo = '';
searchParams.anchorName = '';
searchParams.dateRange = [];
pagination.currentPage = 1;
pagination.pageSize = 10;
};
const handleSizeChange = (size: number) => {
pagination.pageSize = size;
handleSearch();
};
const handleCurrentChange = (current: number) => {
pagination.currentPage = current;
handleSearch();
};
const handleOrderDetail = (id: number) => {
// 使用模拟数据
orderDetail.value = getMockOrderDetail(id);
dialogVisible.value = true;
};
onMounted(() => {
handleSearch();
});
</script>
<style scoped>
.trade-operation-distribution-order {
padding: 20px;
}
.card-header {
font-size: 16px;
font-weight: bold;
}
.search-container {
margin-bottom: 20px;
}
.search-form {
display: flex;
align-items: center;
}
.order-list {
margin-top: 20px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.order-detail {
padding: 20px 0;
}
.detail-section {
margin-top: 30px;
}
.detail-section h3 {
font-size: 14px;
font-weight: bold;
margin-bottom: 15px;
color: #606266;
}
</style>