|
@@ -0,0 +1,440 @@
|
|
|
+<template>
|
|
|
+ <div class="order-statistics-container">
|
|
|
+ <el-card class="search-card">
|
|
|
+ <div class="search-header">
|
|
|
+ <span class="title">订单统计</span>
|
|
|
+ <div class="search-actions">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="dateRange"
|
|
|
+ type="daterange"
|
|
|
+ align="right"
|
|
|
+ unlink-panels
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ :picker-options="pickerOptions"
|
|
|
+ @change="handleDateChange"
|
|
|
+ />
|
|
|
+ <el-button type="primary" @click="handleSearch" :loading="loading">查询</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <el-row :gutter="20" class="statistics-row">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <div class="statistic-item">
|
|
|
+ <div class="statistic-title">订单总量</div>
|
|
|
+ <div class="statistic-value">{{ statistics.orderCount }}</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <div class="statistic-item" @click="fetchOrderDetails" style="cursor: pointer;">
|
|
|
+ <div class="statistic-title">总金额</div>
|
|
|
+ <div class="statistic-value">¥{{ statistics.totalAmount | formatMoney }}</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <div class="statistic-item">
|
|
|
+ <div class="statistic-title">成交率</div>
|
|
|
+ <div class="statistic-value">{{ statistics.successRate }}%</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <div class="statistic-item">
|
|
|
+ <div class="statistic-title">退货率</div>
|
|
|
+ <div class="statistic-value">{{ statistics.returnRate }}%</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 订单详情对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ title="订单详情"
|
|
|
+ :visible.sync="detailModalVisible"
|
|
|
+ width="80%"
|
|
|
+ z-index = "999"
|
|
|
+ size="75%"
|
|
|
+ >
|
|
|
+
|
|
|
+ <div>
|
|
|
+ <el-table style="height: 680px" v-loading="detailLoading" :data="orderDetails" border>
|
|
|
+ <el-table-column label="订单号" align="center" prop="orderCode" width="200px" />
|
|
|
+ <el-table-column label="所属公司" align="center" prop="companyName" />
|
|
|
+ <el-table-column label="所属员工" align="center" prop="companyUserNickName" />
|
|
|
+ <el-table-column label="用户昵称" align="center" prop="nickname" width="150px" >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span>{{scope.row.nickname}} </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="收件人" align="center" prop="realName" width="150px" >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span>{{scope.row.realName}} </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <!-- <el-table-column label="商品" align="center" width="300px" >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <div v-for="(item, index) in scope.row.items" class="items" >
|
|
|
+ <img class="pic" :src="JSON.parse(item.jsonInfo).image" />
|
|
|
+ <div class="goods-content">
|
|
|
+ <div class="goods-title">{{ JSON.parse(item.jsonInfo).productName}}</div>
|
|
|
+ <div class="sku">{{ JSON.parse(item.jsonInfo).sku}}</div>
|
|
|
+ <div class="price">¥{{JSON.parse(item.jsonInfo).price}}×{{item.num}}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column> -->
|
|
|
+ <el-table-column label="订单金额" align="center" prop="totalPrice" >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span v-if="scope.row.totalPrice!=null">{{scope.row.totalPrice.toFixed(2)}}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="应付金额" align="center" prop="payPrice" >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span v-if="scope.row.payPrice!=null">{{scope.row.payPrice.toFixed(2)}}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="下单时间" align="center" prop="createTime" />
|
|
|
+ <!-- <el-table-column label="支付状态" align="center" prop="paid" /> -->
|
|
|
+ <el-table-column label="支付时间" align="center" prop="payTime" width="180">
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="支付方式" align="center" prop="payType" >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag prop="payType" v-for="(item, index) in payTypeOptions" v-if="scope.row.payType==item.dictValue">{{item.dictLabel}}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="订单类型" align="center" prop="orderType" >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag prop="status" v-for="(item, index) in orderTypeOptions" v-if="scope.row.orderType==item.dictValue">{{item.dictLabel}}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" align="center" prop="status" >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag prop="status" v-for="(item, index) in statusOptions" v-if="scope.row.status==item.dictValue">{{item.dictLabel}}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="物流状态" align="center" prop="deliveryStatus" >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag prop="status" v-for="(item, index) in deliveryStatusOptions" v-if="scope.row.deliveryStatus==item.dictValue">{{item.dictLabel}}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="物流结算状态" align="center" prop="deliveryPayStatus" >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag prop="status" v-for="(item, index) in deliveryPayStatusOptions" v-if="scope.row.deliveryPayStatus==item.dictValue">{{item.dictLabel}}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" fixed="right" width="80px" align="center" class-name="small-padding fixed-width">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-button
|
|
|
+ size="mini"
|
|
|
+ type="text"
|
|
|
+ @click="handleDetails(scope.row)"
|
|
|
+ v-hasPermi="['store:storeOrder:query']"
|
|
|
+ >查看</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <pagination
|
|
|
+ v-show="pagination.total>0"
|
|
|
+ :total="pagination.total"
|
|
|
+ :page.sync="pagination.currentPage"
|
|
|
+ :limit.sync="pagination.pageSize"
|
|
|
+ @pagination="fetchOrderDetails"
|
|
|
+ />
|
|
|
+ <el-drawer
|
|
|
+ z-index = "999"
|
|
|
+ size="75%"
|
|
|
+ :title="show.title" :visible.sync="show.open"
|
|
|
+ >
|
|
|
+ <product-order ref="order" />
|
|
|
+ </el-drawer>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getOrderStatistics } from "@/api/store/statistics";
|
|
|
+import { listStoreOrder } from "@/api/store/storeOrder";
|
|
|
+import productOrder from "../components/productOrder";
|
|
|
+export default {
|
|
|
+ components: { productOrder },
|
|
|
+ name: 'OrderStatistics',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ deliveryPayStatusOptions:[],
|
|
|
+ deliveryStatusOptions:[],
|
|
|
+ orderTypeOptions:[],
|
|
|
+ payTypeOptions:[],
|
|
|
+ show:{
|
|
|
+ open:false,
|
|
|
+ title:"订单详情"
|
|
|
+ },
|
|
|
+ statusOptions:[],
|
|
|
+ dateRange: [],
|
|
|
+ pickerOptions: {
|
|
|
+ shortcuts: [{
|
|
|
+ text: '最近一周',
|
|
|
+ onClick(picker) {
|
|
|
+ const end = new Date();
|
|
|
+ const start = new Date();
|
|
|
+ start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
|
|
+ picker.$emit('pick', [start, end]);
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ text: '最近一个月',
|
|
|
+ onClick(picker) {
|
|
|
+ const end = new Date();
|
|
|
+ const start = new Date();
|
|
|
+ start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
|
|
+ picker.$emit('pick', [start, end]);
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ text: '最近三个月',
|
|
|
+ onClick(picker) {
|
|
|
+ const end = new Date();
|
|
|
+ const start = new Date();
|
|
|
+ start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
|
|
+ picker.$emit('pick', [start, end]);
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ loading: false,
|
|
|
+ detailLoading: false,
|
|
|
+ statistics: {
|
|
|
+ orderCount: 0,
|
|
|
+ totalAmount: 0,
|
|
|
+ successRate: 0,
|
|
|
+ returnRate: 0
|
|
|
+ },
|
|
|
+ orderDetails: [],
|
|
|
+ detailModalVisible: false,
|
|
|
+ pagination: {
|
|
|
+ currentPage: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0
|
|
|
+ },
|
|
|
+
|
|
|
+ };
|
|
|
+ },
|
|
|
+ filters: {
|
|
|
+ formatMoney(value) {
|
|
|
+ if (!value) return '0.00';
|
|
|
+ return parseFloat(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.getDicts("store_order_type").then((response) => {
|
|
|
+ this.orderTypeOptions = response.data;
|
|
|
+ });
|
|
|
+ this.getDicts("user_status").then((response) => {
|
|
|
+ this.userStatusOptions = response.data;
|
|
|
+ });
|
|
|
+ this.getDicts("store_pay_type").then((response) => {
|
|
|
+ this.payTypeOptions = response.data;
|
|
|
+ });
|
|
|
+ this.getDicts("store_order_status").then((response) => {
|
|
|
+ this.statusOptions = response.data;
|
|
|
+ });
|
|
|
+ this.getDicts("store_order_delivery_status").then((response) => {
|
|
|
+ this.deliveryStatusOptions = response.data;
|
|
|
+ });
|
|
|
+ this.getDicts("store_delivery_pay_status").then((response) => {
|
|
|
+ this.deliveryPayStatusOptions = response.data;
|
|
|
+ });
|
|
|
+ // 默认查询最近30天数据
|
|
|
+ const end = new Date();
|
|
|
+ const start = new Date();
|
|
|
+ start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
|
|
+ this.dateRange = [start, end];
|
|
|
+ this.fetchStatistics();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ handleDetails(row){
|
|
|
+ this.show.open=true;
|
|
|
+ const orderId = row.id ;
|
|
|
+ setTimeout(() => {
|
|
|
+ this.$refs.order.getOrder(orderId);
|
|
|
+ }, 500);
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 格式化日期(内置方法,替代外部工具函数)
|
|
|
+ * @param {Date|string} date 日期对象或字符串
|
|
|
+ * @param {string} [fmt='yyyy-MM-dd HH:mm:ss'] 格式字符串
|
|
|
+ * @returns {string} 格式化后的日期字符串
|
|
|
+ */
|
|
|
+ formatDate(date, fmt = 'yyyy-MM-dd HH:mm:ss') {
|
|
|
+ if (!date) return '';
|
|
|
+ if (typeof date === 'string') {
|
|
|
+ date = new Date(date.replace(/-/g, '/'));
|
|
|
+ }
|
|
|
+ if (!(date instanceof Date)) {
|
|
|
+ date = new Date(date);
|
|
|
+ }
|
|
|
+
|
|
|
+ const o = {
|
|
|
+ 'M+': date.getMonth() + 1, // 月份
|
|
|
+ 'd+': date.getDate(), // 日
|
|
|
+ 'H+': date.getHours(), // 小时
|
|
|
+ 'm+': date.getMinutes(), // 分
|
|
|
+ 's+': date.getSeconds(), // 秒
|
|
|
+ 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
|
|
|
+ 'S': date.getMilliseconds() // 毫秒
|
|
|
+ };
|
|
|
+
|
|
|
+ if (/(y+)/.test(fmt)) {
|
|
|
+ fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const k in o) {
|
|
|
+ if (new RegExp('(' + k + ')').test(fmt)) {
|
|
|
+ fmt = fmt.replace(
|
|
|
+ RegExp.$1,
|
|
|
+ RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt;
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ handleDateChange(val) {
|
|
|
+ this.dateRange = val;
|
|
|
+ this.fetchStatistics();
|
|
|
+ },
|
|
|
+
|
|
|
+ handleSearch() {
|
|
|
+ this.fetchStatistics();
|
|
|
+ },
|
|
|
+
|
|
|
+ async fetchStatistics() {
|
|
|
+ if (!this.dateRange || this.dateRange.length !== 2) {
|
|
|
+ this.$message.warning('请选择日期范围');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loading = true;
|
|
|
+ try {
|
|
|
+ const params = {
|
|
|
+ startTime: this.formatDate(this.dateRange[0], 'yyyy-MM-dd'),
|
|
|
+ endTime: this.formatDate(this.dateRange[1], 'yyyy-MM-dd')
|
|
|
+ };
|
|
|
+ const response = await getOrderStatistics(params);
|
|
|
+ this.statistics = response.data;
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error('获取统计数据失败');
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async fetchOrderDetails() {
|
|
|
+ if (!this.dateRange || this.dateRange.length !== 2) return;
|
|
|
+
|
|
|
+ this.detailLoading = true;
|
|
|
+ try {
|
|
|
+ const params = {
|
|
|
+ beginTime: this.formatDate(this.dateRange[0], 'yyyy-MM-dd'),
|
|
|
+ endTime: this.formatDate(this.dateRange[1], 'yyyy-MM-dd'),
|
|
|
+ pageNum: this.pagination.currentPage,
|
|
|
+ pageSize: this.pagination.pageSize,
|
|
|
+ paid:1
|
|
|
+ };
|
|
|
+ const response = await listStoreOrder(params);
|
|
|
+ this.orderDetails = response.rows;
|
|
|
+ this.pagination.total = response.total;
|
|
|
+ this.detailModalVisible = true;
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error('获取订单详情失败');
|
|
|
+ } finally {
|
|
|
+ this.detailLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleSizeChange(val) {
|
|
|
+ this.pagination.pageSize = val;
|
|
|
+ this.fetchOrderDetails();
|
|
|
+ },
|
|
|
+
|
|
|
+ handleCurrentChange(val) {
|
|
|
+ this.pagination.currentPage = val;
|
|
|
+ this.fetchOrderDetails();
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.order-statistics-container {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-card {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.statistics-row {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.statistic-item {
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.statistic-title {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #999;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.statistic-value {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.statistic-compare {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.statistic-compare .up {
|
|
|
+ color: #f56c6c;
|
|
|
+ margin-left: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.statistic-compare .down {
|
|
|
+ color: #67c23a;
|
|
|
+ margin-left: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.pagination-container {
|
|
|
+ margin-top: 20px;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+</style>
|