Compare commits

..

2 Commits

7 changed files with 265 additions and 74 deletions

View File

@ -237,6 +237,7 @@
.table-cell-img-box { .table-cell-img-box {
width: 60px; width: 60px;
height: 60px; height: 60px;
text-align: center;
overflow: hidden; /* 隐藏超出部分 */ overflow: hidden; /* 隐藏超出部分 */
display: flex; /* 使用 Flex 布局居中 */ display: flex; /* 使用 Flex 布局居中 */
justify-content: center; justify-content: center;

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="custom-table-container"> <div class="custom-table-container">
<div class="custom-table-tool"> <div class="custom-table-tool" v-if="showPagination">
<el-form :inline="true" class="demo-form-inline" :label-width="'auto'"> <el-form :inline="true" class="demo-form-inline" :label-width="'auto'">
<el-form-item label="每页显示" style="margin-bottom: 10px"> <el-form-item label="每页显示" style="margin-bottom: 10px">
<el-select <el-select

View File

@ -317,7 +317,7 @@ export const constantRoutes = [
name: 'dataBoard', name: 'dataBoard',
hidden: false, hidden: false,
meta: { meta: {
title: "电商数据看板", title: "交易分析",
icon: "", icon: "",
noCache: false, noCache: false,
link: null, link: null,

View File

@ -149,7 +149,7 @@
</div> </div>
<div v-for="(item, index) in formInline.attribute" :key="index" class="attr-row"> <div v-for="(item, index) in formInline.attribute" :key="index" class="attr-row">
<div class="attr-input">{{ item.name }}</div> <div class="attr-input">{{ item.name }}</div>
<el-input v-model="item.value" class="attr-input" /> <el-input v-model="item.value" class="attr-input" placeholder="请输入属性内容"/>
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
@ -197,6 +197,9 @@
<myUploadImage v-model="formInline.detailUrl" :isShowSubscript="false"></myUploadImage> <myUploadImage v-model="formInline.detailUrl" :isShowSubscript="false"></myUploadImage>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="商品描述" prop="describeContent">
<el-input v-model="formInline.describeContent" style="width: 400px;" :rows="2" type="textarea" clearable placeholder="请输入商品描述" />
</el-form-item>
<!-- 商品视频 --> <!-- 商品视频 -->
<el-form-item label="商品视频" prop="videoUrl"> <el-form-item label="商品视频" prop="videoUrl">
<div> <div>
@ -280,6 +283,7 @@ const formInline = reactive({
] ]
}, // }, //
detailUrl: "", detailUrl: "",
describeContent: "",
brandId: "", brandId: "",
traceCode: "", traceCode: "",
videoUrl: "", videoUrl: "",

View File

@ -145,7 +145,7 @@
</div> </div>
<div v-for="(item, index) in formInline.attribute" :key="index" class="attr-row"> <div v-for="(item, index) in formInline.attribute" :key="index" class="attr-row">
<div class="attr-input">{{ item.name }}</div> <div class="attr-input">{{ item.name }}</div>
<el-input v-model="item.value" class="attr-input" /> <el-input v-model="item.value" class="attr-input" placeholder="请输入属性内容" />
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
@ -194,6 +194,9 @@
<myUploadImage v-model="formInline.detailUrl" :isShowSubscript="false"></myUploadImage> <myUploadImage v-model="formInline.detailUrl" :isShowSubscript="false"></myUploadImage>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="商品描述" prop="describeContent">
<el-input v-model="formInline.describeContent" style="width: 400px;" :rows="2" type="textarea" clearable placeholder="请输入商品描述" />
</el-form-item>
<!-- 商品视频 --> <!-- 商品视频 -->
<el-form-item label="商品视频" prop="videoUrl"> <el-form-item label="商品视频" prop="videoUrl">
<div> <div>
@ -280,6 +283,7 @@ const formInline = reactive({
] ]
}, // }, //
detailUrl: "", detailUrl: "",
describeContent: "",
brandId: "", brandId: "",
traceCode: "", traceCode: "",
videoUrl: "", videoUrl: "",

View File

@ -150,7 +150,7 @@
</div> </div>
<div v-for="(item, index) in formInline.attribute" :key="index" class="attr-row"> <div v-for="(item, index) in formInline.attribute" :key="index" class="attr-row">
<div class="attr-input">{{ item.name }}</div> <div class="attr-input">{{ item.name }}</div>
<el-input v-model="item.value" class="attr-input" /> <el-input v-model="item.value" class="attr-input" placeholder="请输入属性内容" />
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
@ -200,6 +200,9 @@
<img v-for="(item, index) in formInline.detailUrl.split(',')" :key="index" style="width: 100px;margin-right: 5px;" :src="item" alt=""> <img v-for="(item, index) in formInline.detailUrl.split(',')" :key="index" style="width: 100px;margin-right: 5px;" :src="item" alt="">
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="商品描述" prop="describeContent">
<el-input v-model="formInline.describeContent" style="width: 400px;" :rows="2" type="textarea" clearable placeholder="请输入商品描述" />
</el-form-item>
<!-- 商品视频 --> <!-- 商品视频 -->
<el-form-item label="商品视频" prop="videoUrl" v-if="formInline.videoUrl"> <el-form-item label="商品视频" prop="videoUrl" v-if="formInline.videoUrl">
<div> <div>
@ -288,6 +291,7 @@ const formInline = reactive({
] ]
}, // }, //
detailUrl: "", detailUrl: "",
describeContent: "",
brandId: "", brandId: "",
traceCode: "", traceCode: "",
videoUrl: "", videoUrl: "",

View File

@ -58,79 +58,135 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 中间模块 -->
<div class="app-container-list"> <div class="app-container-list">
<div class="app-container-block"> <div class="app-container-block" v-for="(item,index) in middleData" :key="index">
<div class="app-container-block-title">销售总额</div> <div class="app-container-block-title">{{ item.title }}
<div class="app-container-block-price"> <el-popover
15698<text style="font-size: 10px; color: #999999; margin-left: 10px" title=""
></text :content="item.tooltip"
placement="left-start"
> >
<template #reference>
<el-icon color="#c5c5c5" style="float: right;"><QuestionFilled /></el-icon>
</template>
</el-popover>
</div>
<div class="app-container-block-price">
{{ item.value }}
<text style="font-size: 12px; color: #999999; margin-left: 5px">
{{ item.unit }}
</text>
</div> </div>
<div class="app-container-block-proportion"> <div class="app-container-block-proportion">
环比<text style="color: red; font-size: 12px">2.6%</text> {{ item.ratioMethod }}
</div> <text :style="{'font-size': '12px',color: item.ratioStatus == '下降'?'red' : 'green'}">
</div> <el-icon v-if="item.ratioStatus == '下降'"><CaretBottom /></el-icon>
<div class="app-container-block"> <el-icon v-else><CaretTop /></el-icon>
<div class="app-container-block-title">订单总数</div> {{ item.ratioValue }}
<div class="app-container-block-price"> </text>
52<text style="font-size: 10px; color: #999999; margin-left: 10px"
></text
>
</div>
<div class="app-container-block-proportion">
环比<text style="color: red; font-size: 12px">2.6%</text>
</div>
</div>
<div class="app-container-block">
<div class="app-container-block-title">支付转化率</div>
<div class="app-container-block-price">
16.56<text style="font-size: 10px; color: #999999; margin-left: 10px"
>%</text
>
</div>
<div class="app-container-block-proportion">
环比<text style="color: red; font-size: 12px">2.6%</text>
</div>
</div>
<div class="app-container-block">
<div class="app-container-block-title">客单价</div>
<div class="app-container-block-price">
12.33<text style="font-size: 10px; color: #999999; margin-left: 10px"
></text
>
</div>
<div class="app-container-block-proportion">
环比<text style="color: red; font-size: 12px">2.6%</text>
</div>
</div>
<div class="app-container-block">
<div class="app-container-block-title">成功退款金额</div>
<div class="app-container-block-price">
152.5<text style="font-size: 10px; color: #999999; margin-left: 10px"
></text
>
</div>
<div class="app-container-block-proportion">
环比<text style="color: red; font-size: 12px">2.6%</text>
</div>
</div>
<div class="app-container-block">
<div class="app-container-block-title">浏览量</div>
<div class="app-container-block-price">
156988<text style="font-size: 10px; color: #999999; margin-left: 10px"
></text
>
</div>
<div class="app-container-block-proportion">
环比<text style="color: red; font-size: 12px">2.6%</text>
</div> </div>
</div> </div>
</div> </div>
<!-- 底部模块 -->
<div class="bottom-flex-box">
<div class="bottom-box-item">
<div class="bottom-box-item-title">商品销量</div>
<div class="bottom-box-item-content">
<tableComponent
:table-data="tableData"
:columns="columns"
:show-border="false"
:show-pagination="false"
>
<!-- 自定义-排名图片 -->
<template #ranking="slotProps">
<div class="table-cell-img-box" style="width: 40px;height: 40px;" v-if="slotProps.row.ranking == 1">
<img :src="img1" class="table-cell-img" alt="" />
</div>
<div class="table-cell-img-box" style="width: 40px;height: 40px;" v-else-if="slotProps.row.ranking == 2">
<img :src="img2" class="table-cell-img" alt="" />
</div>
<div class="table-cell-img-box" style="width: 40px;height: 40px;" v-else-if="slotProps.row.ranking == 3">
<img :src="img3" class="table-cell-img" alt="" />
</div>
<div v-else>{{ slotProps.row.ranking }}</div>
</template>
<!-- 自定义-商品图片 -->
<template #goodUrl="slotProps">
<div class="table-cell-img-box" style="width: 40px;height: 40px;">
<img :src="slotProps.row.goodUrl" class="table-cell-img" alt="" />
</div>
</template>
</tableComponent>
</div>
</div>
<div class="bottom-box-item">
<div class="bottom-box-item-title">售后概况</div>
<div class="bottom-box-item-content">
<!-- -->
<div class="bottom-box-summary" style="margin: 20px 0;">
<div class="bottom-box-summary-item">
<div class="bottom-box-summary-title">售后订单数量()</div>
<div class="bottom-box-summary-value">{{ afterSalesData.totalAfterSalesOrders }}</div>
</div>
<div class="bottom-box-summary-item">
<div class="bottom-box-summary-title">仅退款订单数量()</div>
<div class="bottom-box-summary-value">{{ afterSalesData.totalRefundOrders }}</div>
</div>
<div class="bottom-box-summary-item">
<div class="bottom-box-summary-title">退货退款订单数量()</div>
<div class="bottom-box-summary-value">{{ afterSalesData.totalReturnOrders }}</div>
</div>
</div>
<!-- -->
<div class="bottom-box-summary" style="line-height: 30px;margin: 0;">
<div class="bottom-box-summary-item" style="width: 45%; ">
<img :src="alert" style="width: 30px;height: 30px;" alt="" />
<div class="bottom-box-summary-title" style="display: inline-block;vertical-align: middle;margin-left: 8px;">预警商品</div>
</div>
<div class="bottom-box-summary-item" style="width: 100px;flex: initial;">
<div class="bottom-box-summary-title">退款订单量</div>
</div>
<div class="bottom-box-summary-item" style="width: 100px;flex: initial;">
<div class="bottom-box-summary-title">退款金额</div>
</div>
</div>
<div class="bottom-box-summary" style="line-height: 40px;margin: 0;"
v-for="(item, index) in afterSalesData.data" :key="index">
<div class="bottom-box-summary-item" style="width: 30%; display: flex;">
<img :src="item.goodUrl" style="width: 40px;height: 40px;" alt="" />
<div class="bottom-box-summary-title text-ellipsis" style="flex: 1; vertical-align: top;color: #000;margin-left: 8px;" title="耿马西红柿耿马西红柿耿马西红柿耿马西红柿">
{{ item.goodInfo }}
</div>
</div>
<div class="bottom-box-summary-item" style="width: 100px;flex: initial;">
<div class="bottom-box-summary-title" style="color: #000;">{{ item.refundedOrders }}</div>
</div>
<div class="bottom-box-summary-item" style="width: 100px;flex: initial;">
<div class="bottom-box-summary-title" style="color: #000;">{{ item.refundAmount }}</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import Mock from "mockjs";
import tableComponent from "@/components/tableComponent.vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import img1 from "@/assets/images/first.png";
import img2 from "@/assets/images/second.png";
import img3 from "@/assets/images/third.png";
import alert from "@/assets/images/alert.png";
let tomato = ref("http://gov-cloud.oss-cn-chengdu.aliyuncs.com/backend/a866613495ed4678957a4440b8d3776c.png");
// DOM // DOM
const chartRef = ref(null); const chartRef = ref(null);
// ECharts // ECharts
@ -138,7 +194,7 @@ let chartInstance = null;
// //
const colorList = ['#3685FE', '#FFD500', '#25BF82']; const colorList = ['#3685FE', '#FFD500', '#25BF82'];
// x // x
const xData = ['1', '2月', '3月', '4月', '5月', '6月', '7月']; const xData = ['1', '2月', '3月', '4月', '5月', '6月', '7月'];
// //
const option = { const option = {
@ -150,12 +206,12 @@ const option = {
fontWeight: 400, fontWeight: 400,
}, },
left: 'center', left: 'center',
top: '5%', top: '0%',
}, },
legend: { legend: {
icon: 'circle', icon: 'circle',
top: '0', top: '0',
right: '5%', right: '30%',
itemWidth: 6, itemWidth: 6,
itemGap: 20, itemGap: 20,
textStyle: { textStyle: {
@ -202,6 +258,9 @@ const option = {
}, },
grid: { grid: {
top: '20%', top: '20%',
left: 20,
right: 20,
bottom: 30, //
}, },
xAxis: [ xAxis: [
{ {
@ -456,8 +515,72 @@ const resizeChart = () => {
chartInstance.resize(); chartInstance.resize();
} }
}; };
//
const middleData = ref([]);
const generateMockData = () => {
return Mock.mock({
"list|5": [
{ //
"id|+1": 10000,
title: '@pick(["买家数量", "支付转化率", "客单价", "退款率", "加购数量"])',
value: "@float(10, 200, 2, 2)",
unit: '@pick(["人", "%", "元", "%", "次"])',
tooltip: '@pick(["购买商品人数统计", "支付转化率 = 完成支付的订单数量 + 访问网站的用户数量 x 100%", "客单价是指每一位顾客平均购买商品金额", "退款率 = (退款订单数量 + 总订单数量) x 100%", "用户将商品加入购物车次数"])',
ratioMethod: '@pick(["环比", "同比"])', //
ratioStatus: '@pick(["上升", "下降"])', //
ratioValue: '@float(10, 20, 1, 1)' + '%', //
//
"ranking|+1": 1, //
goodUrl: tomato.value,
goodName: "@cname",
stockToUseRatio: "@float(10, 200, 2, 2)" + "%", //
salesAmount: "@integer(1000, 20000)" + "元", //
},
],
}).list;
};
//
const tableData = ref([]);
const columns = ref([
{ prop: "ranking", label: "排名", slotName: "ranking", width: 66 },
{ prop: "goodUrl", label: "商品", slotName: "goodUrl", width: 66 },
{ prop: "goodName", label: "商品名称", width: "auto" },
{ prop: "stockToUseRatio", label: "存销比" },
{ prop: "salesAmount", label: "销售金额" },
]);
const afterSalesData = ref({
totalAfterSalesOrders: 1128,
totalRefundOrders: 265,
totalReturnOrders: 129,
data: [
{ goodUrl: tomato.value, goodInfo: "耿马西红柿", refundedOrders: "25单", refundAmount: '129元' },
{ goodUrl: tomato.value, goodInfo: "耿马西红柿", refundedOrders: "36单", refundAmount: '265元' },
{ goodUrl: tomato.value, goodInfo: "耿马西红柿", refundedOrders: "51单", refundAmount: '1128元' },
],
});
//
const loadMiddleData = async () => {
middleData.value = generateMockData();
tableData.value = generateMockData();
console.log(tableData.value);
// try {
// let response = await getGoodManageInfo(prams);
// if (response.code == 200) {
// middleData.value = response.data.records;
// middleData.value = response.data.total;
// }
// } catch (error) { }
};
onMounted(() => { onMounted(() => {
initChart(); initChart();
loadMiddleData();
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -485,7 +608,8 @@ onMounted(() => {
margin-top: 20px; margin-top: 20px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: space-between;
gap: 20px;
} }
.app-container-title { .app-container-title {
margin-bottom: 20px; margin-bottom: 20px;
@ -494,7 +618,7 @@ onMounted(() => {
} }
.app-container-data { .app-container-data {
border-radius: 10px; border-radius: 10px;
padding: 20px; padding: 20px 10px 20px 20px;
width: 100%; width: 100%;
background-color: #fff; background-color: #fff;
.app-container-data-left { .app-container-data-left {
@ -512,11 +636,10 @@ onMounted(() => {
} }
} }
.app-container-block { .app-container-block {
width: 250px; flex: 1;
background-color: #fff; background-color: #fff;
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px 10px 10px 14px;
margin-left: 26px;
.app-container-block-title { .app-container-block-title {
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
@ -529,6 +652,61 @@ onMounted(() => {
} }
.app-container-block-proportion { .app-container-block-proportion {
font-size: 12px; font-size: 12px;
.el-icon {
vertical-align: text-top;
}
}
:deep(.el-popper.tooltip-box-item) {
max-width: 200px; /* 限制最大宽度 */
word-break: break-word; /* 长单词或URL换行 */
white-space: normal !important; /* 覆盖默认的 nowrap */
}
.tooltip-box-item {
width: 200px !important;
} }
} }
.bottom-flex-box {
margin-top: 20px;
display: flex;
justify-content: space-between;
gap: 20px;
.bottom-box-item {
flex: 1;
width: 50%;
background-color: #fff;
border-radius: 10px;
padding: 20px 20px 20px 20px;
}
.bottom-box-item-title {
font-size: 18px;
font-weight: bold;
}
.bottom-box-item-content {
height: 350px;
.bottom-box-summary {
display: flex;
margin: 10px 0;
gap: 20px;
.bottom-box-summary-item {
flex: 1;
.bottom-box-summary-title {
font-size: 16px;
color: #999;
margin-bottom: 16px;
}
.bottom-box-summary-value {
font-size: 20px;
color: #000;
font-weight: bold;
}
}
}
}
}
.text-ellipsis {
white-space: nowrap; /* 禁止换行 */
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis; /* 显示省略号 */
width: 100%; /* 必须设置宽度 */
}
</style> </style>