|
@@ -0,0 +1,427 @@
|
|
|
+<template>
|
|
|
+ <div class="content">
|
|
|
+ <div style="background-color: white; padding: 20px">
|
|
|
+ <el-form :inline="true" :model="queryForm">
|
|
|
+ <el-form-item label="国家">
|
|
|
+ <el-select v-model="queryForm.countryId" placeholder="请选择" @change="onQuery">
|
|
|
+ <el-option v-for="item in countryData" :label="item.chineseName" :value="item.id" :key="item.id"> </el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="日期">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryForm.timeArr"
|
|
|
+ type="daterange"
|
|
|
+ unlink-panels
|
|
|
+ range-separator="-"
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ @change="onQuery" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="onQuery">搜索</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ <el-row style="margin-top: 20px" type="flex">
|
|
|
+ <el-col :span="15">
|
|
|
+ <div style="margin-right: 20px; background-color: white; padding: 20px 0 0 20px; height: 100%">
|
|
|
+ <TitleInfo :content="'产品统计'"></TitleInfo>
|
|
|
+ <div style="padding-top: 20px; display: flex; flex-wrap: wrap" v-loading="loadingOne">
|
|
|
+ <div style="width: 33%; padding: 0 20px 20px 0">
|
|
|
+ <div style="padding: 20px; background-color: #f4f4f5; border-radius: 5px; font-size: 12px !important; color: #909399 !important">
|
|
|
+ <div style="margin-bottom: 20px; display: flex">
|
|
|
+ <div style="background: #0084ff; padding: 5px">
|
|
|
+ <img style="width: 20px; height: 20px; border-radius: 5px" src="@/assets/images/portrait/iconm_kehd.png" alt="" />
|
|
|
+ </div>
|
|
|
+ <div style="margin-left: 20px; height: 30px; line-height: 30px; font-weight: 700">总计</div>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex">
|
|
|
+ <div style="width: 50%">
|
|
|
+ <span>新增 (款)</span>
|
|
|
+ <span style="color: black; font-weight: 700; margin-left: 20px">{{ allData.productStatistics.newTotal }}</span>
|
|
|
+ </div>
|
|
|
+ <div style="width: 50%">
|
|
|
+ <span>总计 (款)</span>
|
|
|
+ <span style="color: black; font-weight: 700; margin-left: 20px">{{ allData.productStatistics.total }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <template v-if="productType && productType.length > 0">
|
|
|
+ <div style="width: 33%; padding: 0 20px 20px 0" v-for="(item, index) in productType" :key="index">
|
|
|
+ <div style="padding: 20px; background-color: #f4f4f5; border-radius: 5px; font-size: 12px !important; color: #909399 !important">
|
|
|
+ <div style="margin-bottom: 20px; display: flex">
|
|
|
+ <div style="background: #0084ff; padding: 5px">
|
|
|
+ <img style="width: 20px; height: 20px; border-radius: 5px" src="@/assets/images/portrait/iconm_kehd.png" alt="" />
|
|
|
+ </div>
|
|
|
+ <div style="margin-left: 20px; height: 30px; line-height: 30px; font-weight: 700">{{ item.label }}</div>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex">
|
|
|
+ <div style="width: 50%">
|
|
|
+ <span>新增 (款)</span>
|
|
|
+ <span style="color: black; font-weight: 700; margin-left: 20px">{{ getNum(item, "typeNewTotal") }}</span>
|
|
|
+ </div>
|
|
|
+ <div style="width: 50%">
|
|
|
+ <span>总计 (款)</span>
|
|
|
+ <span style="color: black; font-weight: 700; margin-left: 20px">{{ getNum(item, "typeTotal") }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="9">
|
|
|
+ <div style="background-color: white; padding: 20px; height: 100%">
|
|
|
+ <TitleInfo :content="'产品分布'"></TitleInfo>
|
|
|
+ <div ref="mainOne" style="height: calc(100% - 24px)"></div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row style="margin-top: 20px" type="flex">
|
|
|
+ <el-col :span="12">
|
|
|
+ <div style="margin-right: 20px; background-color: white; padding: 20px; height: 100%">
|
|
|
+ <TitleInfo :content="'产品类型排行'"></TitleInfo>
|
|
|
+ <div style="padding-top: 20px" v-loading="loadingTwo">
|
|
|
+ <el-table
|
|
|
+ :data="allData.productTypeRanking"
|
|
|
+ :default-sort="{ prop: 'contractQuantity', order: 'descending' }"
|
|
|
+ style="width: 100%"
|
|
|
+ max-height="30vh"
|
|
|
+ @sort-change="sortChangeTwo">
|
|
|
+ <el-table-column label="产品类型" prop="name" min-width="140" />
|
|
|
+ <el-table-column label="销售量" prop="contractQuantity" sortable width="120" />
|
|
|
+ <el-table-column label="销售额" prop="contractAmount" sortable width="120" />
|
|
|
+ <el-table-column label="采购量" prop="purchaseQuantity" sortable width="120" />
|
|
|
+ <el-table-column label="采购额" prop="purchaseAmount" sortable width="120" />
|
|
|
+ <!-- <el-table-column label="生产总量" prop="name" sortable width="120" /> -->
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <div style="background-color: white; padding: 20px; height: 100%">
|
|
|
+ <TitleInfo :content="'产品排行'"></TitleInfo>
|
|
|
+ <div style="padding-top: 20px" v-loading="loadingThree">
|
|
|
+ <el-table
|
|
|
+ :data="allData.productRanking"
|
|
|
+ :default-sort="{ prop: 'contractQuantity', order: 'descending' }"
|
|
|
+ style="width: 100%"
|
|
|
+ max-height="30vh"
|
|
|
+ @sort-change="sortChangeThree">
|
|
|
+ <el-table-column label="产品名称" prop="name" min-width="140" />
|
|
|
+ <el-table-column label="销售量" prop="contractQuantity" sortable width="120" />
|
|
|
+ <el-table-column label="销售额" prop="contractAmount" sortable width="120" />
|
|
|
+ <el-table-column label="采购量" prop="purchaseQuantity" sortable width="120" />
|
|
|
+ <el-table-column label="采购额" prop="purchaseAmount" sortable width="120" />
|
|
|
+ <!-- <el-table-column label="生产总量" prop="name" sortable width="120" /> -->
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <div style="margin-top: 20px; background-color: white; padding: 20px">
|
|
|
+ <TitleInfo :content="'销售趋势'"></TitleInfo>
|
|
|
+ <div ref="mainTwo" style="height: 40vh"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import * as echarts from "echarts";
|
|
|
+import TitleInfo from "@/components/TitleInfo/index.vue";
|
|
|
+import { reactive, ref } from "vue";
|
|
|
+
|
|
|
+const { proxy } = getCurrentInstance();
|
|
|
+const productType = ref([]);
|
|
|
+const countryData = ref([]);
|
|
|
+const queryForm = reactive({
|
|
|
+ countryId: "",
|
|
|
+ beginTime: "",
|
|
|
+ endTime: "",
|
|
|
+ timeArr: [],
|
|
|
+});
|
|
|
+const getDict = () => {
|
|
|
+ proxy.getDictOne(["product_type"]).then((res) => {
|
|
|
+ productType.value = res["product_type"].map((x) => ({
|
|
|
+ label: x.dictValue,
|
|
|
+ value: x.dictKey,
|
|
|
+ }));
|
|
|
+ });
|
|
|
+};
|
|
|
+getDict();
|
|
|
+const getCountryData = () => {
|
|
|
+ proxy.post("/areaInfo/list", { parentId: "0" }).then((res) => {
|
|
|
+ countryData.value = res;
|
|
|
+ let endData = new Date();
|
|
|
+ let beginDate = new Date();
|
|
|
+ beginDate.setFullYear(endData.getFullYear() - 1);
|
|
|
+ queryForm.timeArr = [proxy.parseTime(beginDate, "{y}-{m}-{d}"), proxy.parseTime(endData, "{y}-{m}-{d}")];
|
|
|
+ queryForm.beginTime = queryForm.timeArr[0];
|
|
|
+ queryForm.endTime = queryForm.timeArr[1];
|
|
|
+ getData();
|
|
|
+ });
|
|
|
+};
|
|
|
+getCountryData();
|
|
|
+const onQuery = () => {
|
|
|
+ queryForm.beginTime = queryForm.timeArr[0];
|
|
|
+ queryForm.endTime = queryForm.timeArr[1];
|
|
|
+ getData();
|
|
|
+};
|
|
|
+const loadingOne = ref(null);
|
|
|
+const loadingTwo = ref(null);
|
|
|
+const loadingThree = ref(null);
|
|
|
+const allData = reactive({
|
|
|
+ productStatistics: {
|
|
|
+ newTotal: "",
|
|
|
+ total: "",
|
|
|
+ typeList: [],
|
|
|
+ },
|
|
|
+ productDistribution: [],
|
|
|
+ productTypeRanking: [],
|
|
|
+ productRanking: [],
|
|
|
+});
|
|
|
+const getNum = (item, label) => {
|
|
|
+ let text = "";
|
|
|
+ if (allData.productStatistics.typeList && allData.productStatistics.typeList.length > 0) {
|
|
|
+ let data = allData.productStatistics.typeList.filter((row) => row.type === item.value);
|
|
|
+ if (data && data.length > 0) {
|
|
|
+ text = data[0][label];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return text;
|
|
|
+};
|
|
|
+const optionOne = reactive({
|
|
|
+ data: {
|
|
|
+ tooltip: {
|
|
|
+ trigger: "item",
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ top: "5%",
|
|
|
+ left: "center",
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: "产品分布",
|
|
|
+ type: "pie",
|
|
|
+ radius: ["40%", "70%"],
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ itemStyle: {
|
|
|
+ borderRadius: 10,
|
|
|
+ borderColor: "#fff",
|
|
|
+ borderWidth: 2,
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: false,
|
|
|
+ position: "center",
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ fontSize: 40,
|
|
|
+ fontWeight: "bold",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ labelLine: {
|
|
|
+ show: false,
|
|
|
+ },
|
|
|
+ data: [],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+});
|
|
|
+const optionTwo = reactive({
|
|
|
+ data: {
|
|
|
+ tooltip: {
|
|
|
+ trigger: "axis",
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ["月度销售额", "月成交单数"],
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: "3%",
|
|
|
+ right: "4%",
|
|
|
+ bottom: "3%",
|
|
|
+ containLabel: true,
|
|
|
+ },
|
|
|
+ toolbox: {
|
|
|
+ feature: {
|
|
|
+ saveAsImage: {},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ boundaryGap: false,
|
|
|
+ data: [],
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: "value",
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: "月度销售额",
|
|
|
+ type: "line",
|
|
|
+ data: [],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "月成交单数",
|
|
|
+ type: "line",
|
|
|
+ data: [],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+});
|
|
|
+const mainOne = ref(null);
|
|
|
+const mainTwo = ref(null);
|
|
|
+let myChartOne = null;
|
|
|
+let myChartTwo = null;
|
|
|
+const productTypeRankingProp = ref("contractQuantity");
|
|
|
+const productTypeRankingOrder = ref("descending");
|
|
|
+const productTypeRankingSort = ref({
|
|
|
+ contractQuantity: 10,
|
|
|
+ contractAmount: 20,
|
|
|
+ purchaseQuantity: 30,
|
|
|
+ purchaseAmount: 40,
|
|
|
+});
|
|
|
+const productRankingProp = ref("contractQuantity");
|
|
|
+const productRankingOrder = ref("descending");
|
|
|
+const productRankingSort = ref({
|
|
|
+ contractQuantity: 10,
|
|
|
+ contractAmount: 20,
|
|
|
+ purchaseQuantity: 30,
|
|
|
+ purchaseAmount: 40,
|
|
|
+});
|
|
|
+const orderBy = ref({
|
|
|
+ ascending: 10,
|
|
|
+ descending: 20,
|
|
|
+});
|
|
|
+const getData = () => {
|
|
|
+ loadingOne.value = true;
|
|
|
+ proxy.post("/productInfo/productStatistics", queryForm).then(
|
|
|
+ (res) => {
|
|
|
+ allData.productStatistics = res;
|
|
|
+ setTimeout(() => {
|
|
|
+ loadingOne.value = false;
|
|
|
+ }, 200);
|
|
|
+ },
|
|
|
+ (err) => {
|
|
|
+ console.log(err);
|
|
|
+ setTimeout(() => {
|
|
|
+ loadingOne.value = false;
|
|
|
+ }, 200);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ proxy.post("/productInfo/productDistribution", queryForm).then((res) => {
|
|
|
+ if (res && res.length > 0) {
|
|
|
+ optionOne.data.series[0].data = res.map((item) => {
|
|
|
+ return {
|
|
|
+ value: item.count,
|
|
|
+ name: item.name,
|
|
|
+ type: item.type,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ optionOne.data.series[0].data = [];
|
|
|
+ }
|
|
|
+ myChartOne.setOption(optionOne.data);
|
|
|
+ myChartOne.resize();
|
|
|
+ });
|
|
|
+ proxy.post("/contract/saleTrend", queryForm).then((res) => {
|
|
|
+ if (res && res.length > 0) {
|
|
|
+ optionTwo.data.xAxis.data = res.map((item) => {
|
|
|
+ return item.month;
|
|
|
+ });
|
|
|
+ optionTwo.data.series[0].data = res.map((item) => {
|
|
|
+ return item.contractAmount;
|
|
|
+ });
|
|
|
+ optionTwo.data.series[1].data = res.map((item) => {
|
|
|
+ return item.contractQuantity;
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ optionTwo.data.xAxis.data = [];
|
|
|
+ optionTwo.data.series[0].data = [];
|
|
|
+ optionTwo.data.series[1].data = [];
|
|
|
+ }
|
|
|
+ console.log(optionTwo.data);
|
|
|
+ myChartTwo.setOption(optionTwo.data);
|
|
|
+ myChartTwo.resize();
|
|
|
+ });
|
|
|
+ getProductTypeRanking();
|
|
|
+ getProductRanking();
|
|
|
+};
|
|
|
+const sortChangeTwo = ({ prop, order }) => {
|
|
|
+ productTypeRankingProp.value = prop;
|
|
|
+ productTypeRankingOrder.value = order;
|
|
|
+ getProductTypeRanking();
|
|
|
+};
|
|
|
+const sortChangeThree = ({ prop, order }) => {
|
|
|
+ productRankingProp.value = prop;
|
|
|
+ productRankingOrder.value = order;
|
|
|
+ getProductRanking();
|
|
|
+};
|
|
|
+const getProductTypeRanking = () => {
|
|
|
+ loadingTwo.value = true;
|
|
|
+ let query = JSON.parse(JSON.stringify(queryForm));
|
|
|
+ query.pageNum = 1;
|
|
|
+ query.pageSize = 10;
|
|
|
+ query.sort = productTypeRankingSort.value[productTypeRankingProp.value] || 10;
|
|
|
+ query.orderBy = orderBy.value[productTypeRankingOrder.value] || 20;
|
|
|
+ proxy.post("/productInfo/productTypeRanking", query).then(
|
|
|
+ (res) => {
|
|
|
+ // allData.productTypeRanking = res;
|
|
|
+ setTimeout(() => {
|
|
|
+ loadingTwo.value = false;
|
|
|
+ }, 200);
|
|
|
+ },
|
|
|
+ (err) => {
|
|
|
+ console.log(err);
|
|
|
+ setTimeout(() => {
|
|
|
+ loadingTwo.value = false;
|
|
|
+ }, 200);
|
|
|
+ }
|
|
|
+ );
|
|
|
+};
|
|
|
+const getProductRanking = () => {
|
|
|
+ loadingThree.value = true;
|
|
|
+ let query = JSON.parse(JSON.stringify(queryForm));
|
|
|
+ query.pageNum = 1;
|
|
|
+ query.pageSize = 10;
|
|
|
+ query.sort = productRankingSort.value[productRankingProp.value] || 10;
|
|
|
+ query.orderBy = orderBy.value[productRankingOrder.value] || 20;
|
|
|
+ proxy.post("/productInfo/productRanking", query).then(
|
|
|
+ (res) => {
|
|
|
+ allData.productRanking = res.rows;
|
|
|
+ setTimeout(() => {
|
|
|
+ loadingThree.value = false;
|
|
|
+ }, 200);
|
|
|
+ },
|
|
|
+ (err) => {
|
|
|
+ console.log(err);
|
|
|
+ setTimeout(() => {
|
|
|
+ loadingThree.value = false;
|
|
|
+ }, 200);
|
|
|
+ }
|
|
|
+ );
|
|
|
+};
|
|
|
+onMounted(() => {
|
|
|
+ myChartOne = echarts.init(mainOne.value);
|
|
|
+ window.addEventListener("resize", () => {
|
|
|
+ myChartOne.resize();
|
|
|
+ });
|
|
|
+ myChartTwo = echarts.init(mainTwo.value);
|
|
|
+ window.addEventListener("resize", () => {
|
|
|
+ myChartTwo.resize();
|
|
|
+ });
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.content {
|
|
|
+ margin: 20px;
|
|
|
+}
|
|
|
+:deep(.el-form-item) {
|
|
|
+ margin-bottom: 0px;
|
|
|
+}
|
|
|
+</style>
|