Explorar el Código

Merge branch 'hurx'

hurx hace 1 día
padre
commit
4bdb01bb63

+ 8 - 0
src/api/pc/enterprise/order.ts

@@ -313,3 +313,11 @@ export function listOrderStatusLog(params?: any) {
     params: params
   });
 }
+
+// 订单交易分析
+export function customerOrderTradeData() {
+  return request({
+    url: '/order/pcOrder/customerOrderTradeData',
+    method: 'get'
+  });
+}

+ 6 - 1
src/utils/siteConfig.ts

@@ -92,7 +92,12 @@ export const SITE_ROUTES: Record<any, string[]> = {
     '/enterprise/securitySetting/changePhone',
     '/enterprise/changePerson',
     '/enterprise/messageNoticeAdd',
-    '/order/orderEvaluation/evaluation'
+    '/order/orderEvaluation/evaluation',
+    '/analysis/orderAnalysis',
+    '/analysis/purchaseDetail',
+    '/analysis/orderStatus',
+    '/analysis/settlementStatus',
+    '/analysis/deptPurchase'
   ], //订单列表
 
   i: ['/i'], //个人信息

+ 56 - 36
src/views/analysis/orderAnalysis/index.vue

@@ -59,6 +59,7 @@
 import { ref, onMounted } from 'vue';
 import * as echarts from 'echarts';
 import { PageTitle, StatCards } from '@/components';
+import { customerOrderTradeData } from '@/api/pc/enterprise/order';
 
 const trendType = ref('month');
 const categoryType = ref('month');
@@ -67,39 +68,58 @@ const trendChartRef = ref<HTMLElement>();
 const pieChartRef = ref<HTMLElement>();
 const dailyChartRef = ref<HTMLElement>();
 
-const statData = [
-  { value: '10,239.00', label: '购买金额(元)' },
-  { value: '3', label: '购买数量(件)' },
-  { value: '45', label: '订单量(单)' },
-  { value: '78', label: '客单价(元/单)' }
-];
+let trendChart: echarts.ECharts | null = null;
+let pieChart: echarts.ECharts | null = null;
+let dailyChart: echarts.ECharts | null = null;
 
-const categoryData = [
-  { name: '品类01', value: 600 },
-  { name: '品类02', value: 200 },
-  { name: '品类03', value: 200 }
-];
+const statData = ref([
+  { value: '0', label: '购买金额(元)' },
+  { value: '0', label: '购买数量(件)' },
+  { value: '0', label: '订单量(单)' },
+  { value: '0', label: '客单价(元/单)' }
+]);
+
+const categoryData = ref<{ name: string; value: number }[]>([]);
+
+const pieColors = ['#f4c542', '#3498db', '#1abc9c', '#e74c3c', '#9b59b6', '#e67e22', '#2ecc71', '#e60012'];
+
+const fetchData = async () => {
+  const res = await customerOrderTradeData();
+  if (res.code !== 200 || !res.data) return;
+  const data = res.data;
+
+  statData.value = [
+    { value: (data.totalAmount ?? 0).toLocaleString('en-US', { minimumFractionDigits: 2 }), label: '购买金额(元)' },
+    { value: String(data.totalQuantity ?? 0), label: '购买数量(件)' },
+    { value: String(data.orderCount ?? 0), label: '订单量(单)' },
+    { value: (data.avgOrderAmount ?? 0).toLocaleString('en-US', { minimumFractionDigits: 2 }), label: '客单价(元/单)' }
+  ];
+
+  categoryData.value = data.categoryProportion || [];
+
+  initTrendChart(data.amountTrend || []);
+  initPieChart(data.categoryProportion || []);
+  initDailyChart(data.dailyChange || []);
+};
 
 onMounted(() => {
-  initTrendChart();
-  initPieChart();
-  initDailyChart();
+  fetchData();
 });
 
-const initTrendChart = () => {
+const initTrendChart = (amountTrend: { month: string; amount: number }[]) => {
   if (!trendChartRef.value) return;
-  const chart = echarts.init(trendChartRef.value);
-  chart.setOption({
+  if (!trendChart) trendChart = echarts.init(trendChartRef.value);
+  trendChart.setOption({
     grid: { left: 60, right: 20, top: 30, bottom: 30 },
     xAxis: {
       type: 'category',
-      data: ['2025/01', '2025/02', '2025/03', '2025/04', '2025/05', '2025/06', '2025/07', '2025/08', '2025/09', '2025/10', '2025/11', '2025/12'],
+      data: amountTrend.map((d) => d.month),
       axisLine: { lineStyle: { color: '#eee' } },
       axisLabel: { color: '#999' }
     },
     yAxis: {
       type: 'value',
-      name: '销量(万元)',
+      name: '金额(元)',
       nameTextStyle: { color: '#999' },
       axisLine: { show: false },
       splitLine: { lineStyle: { color: '#eee' } },
@@ -108,31 +128,31 @@ const initTrendChart = () => {
     series: [
       {
         type: 'line',
-        data: [300, 980, 450, 600, 400, 550, 900, 700, 650, 800, 500, 750],
+        data: amountTrend.map((d) => d.amount),
         lineStyle: { color: '#e60012', width: 2 },
         itemStyle: { color: '#e60012' },
         symbol: 'circle',
-        symbolSize: 6
+        symbolSize: 6,
+        areaStyle: { color: 'rgba(230, 0, 18, 0.05)' }
       }
     ],
     tooltip: { trigger: 'axis', formatter: (params: any) => `${params[0].name}<br/>¥${params[0].value.toFixed(2)}` }
   });
 };
 
-const initPieChart = () => {
+const initPieChart = (categoryProportion: { name: string; value: number }[]) => {
   if (!pieChartRef.value) return;
-  const chart = echarts.init(pieChartRef.value);
-  chart.setOption({
+  if (!pieChart) pieChart = echarts.init(pieChartRef.value);
+  pieChart.setOption({
     series: [
       {
         type: 'pie',
         radius: ['40%', '70%'],
         center: ['50%', '50%'],
-        data: [
-          { value: 600, name: '品类01', itemStyle: { color: '#f4c542' } },
-          { value: 200, name: '品类02', itemStyle: { color: '#3498db' } },
-          { value: 200, name: '品类03', itemStyle: { color: '#1abc9c' } }
-        ],
+        data: categoryProportion.map((item, idx) => ({
+          ...item,
+          itemStyle: { color: pieColors[idx % pieColors.length] }
+        })),
         label: { formatter: '{b} {d}%', color: '#666' }
       }
     ],
@@ -140,20 +160,20 @@ const initPieChart = () => {
   });
 };
 
-const initDailyChart = () => {
+const initDailyChart = (dailyChange: { day: string; amount: number; quantity: number }[]) => {
   if (!dailyChartRef.value) return;
-  const chart = echarts.init(dailyChartRef.value);
-  chart.setOption({
+  if (!dailyChart) dailyChart = echarts.init(dailyChartRef.value);
+  dailyChart.setOption({
     grid: { left: 60, right: 20, top: 40, bottom: 30 },
     xAxis: {
       type: 'category',
-      data: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
+      data: dailyChange.map((d) => d.day),
       axisLine: { lineStyle: { color: '#eee' } },
       axisLabel: { color: '#999' }
     },
     yAxis: {
       type: 'value',
-      name: '销量(万元)',
+      name: '金额/数量',
       nameTextStyle: { color: '#999' },
       axisLine: { show: false },
       splitLine: { lineStyle: { color: '#eee' } },
@@ -163,7 +183,7 @@ const initDailyChart = () => {
       {
         name: '购买金额',
         type: 'line',
-        data: [400, 500, 350, 600, 450, 640, 500, 550, 400, 600, 450, 500],
+        data: dailyChange.map((d) => d.amount),
         lineStyle: { color: '#f4c542', width: 2 },
         itemStyle: { color: '#f4c542' },
         symbol: 'circle',
@@ -172,7 +192,7 @@ const initDailyChart = () => {
       {
         name: '购买数量',
         type: 'line',
-        data: [300, 400, 250, 500, 350, 332, 400, 450, 300, 500, 350, 400],
+        data: dailyChange.map((d) => d.quantity),
         lineStyle: { color: '#3498db', width: 2 },
         itemStyle: { color: '#3498db' },
         symbol: 'circle',

+ 163 - 137
src/views/analysis/orderStatus/index.vue

@@ -1,22 +1,51 @@
 <template>
   <div class="page-container">
     <PageTitle title="订单执行状态" />
+    <div class="filter-bar">
+      <el-form :model="searchForm" inline>
+        <el-form-item label="订单编号">
+          <el-input v-model="searchForm.keyword" placeholder="请输入订单编号" clearable @clear="handleSearch" />
+        </el-form-item>
+        <el-form-item label="订单日期">
+          <el-date-picker
+            v-model="searchForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="YYYY-MM-DD"
+            @change="handleSearch"
+          />
+        </el-form-item>
+        <el-form-item label="订单状态">
+          <el-select v-model="searchForm.orderStatus" placeholder="请选择订单状态" clearable @change="handleSearch">
+            <el-option v-for="item in orderStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </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>
 
-    <SearchBar :form="searchForm" :filters="filters" />
-
-    <el-table :data="tableData" border style="width: 100%">
+    <el-table v-loading="loading" :data="tableData" border style="width: 100%">
       <el-table-column prop="orderDate" label="下单日期" width="110" align="center" />
-      <el-table-column prop="orderNo" label="订单编号" width="120" align="center" />
-      <el-table-column prop="productName" label="产品" min-width="140" show-overflow-tooltip />
-      <el-table-column prop="quantity" label="数量" width="80" align="center" />
-      <el-table-column prop="price" label="单价" width="100" align="center">
-        <template #default="{ row }">¥{{ row.price.toLocaleString() }}</template>
+      <el-table-column prop="orderNo" label="订单编号" width="140" align="center" />
+      <el-table-column prop="productName" label="产品" min-width="200">
+        <template #default="{ row }">
+          <span class="product-name-text">{{ row.productName }}</span>
+        </template>
       </el-table-column>
+      <el-table-column prop="quantity" label="数量" width="80" align="center" />
+      <!-- <el-table-column prop="price" label="单价" width="100" align="center">
+        <template #default="{ row }">{{ row.price }}</template>
+      </el-table-column> -->
       <el-table-column prop="amount" label="金额" width="100" align="center">
         <template #default="{ row }">¥{{ row.amount.toLocaleString() }}</template>
       </el-table-column>
-      <el-table-column prop="shippedQty" label="发货数量" width="90" align="center" />
-      <el-table-column prop="unshippedQty" label="未发货数量" width="100" align="center" />
+      <!-- <el-table-column prop="shippedQty" label="发货数量" width="90" align="center" />
+      <el-table-column prop="unshippedQty" label="未发货数量" width="100" align="center" /> -->
       <el-table-column prop="status" label="订单状态" width="100" align="center">
         <template #default="{ row }">
           <span :class="['status-tag', getOrderStatusClass(row.status)]">{{ row.status }}</span>
@@ -24,142 +53,139 @@
       </el-table-column>
     </el-table>
 
-    <TablePagination v-model:page="pagination.page" v-model:pageSize="pagination.pageSize" :total="pagination.total" />
+    <TablePagination v-model:page="pagination.page" v-model:pageSize="pagination.pageSize" :total="pagination.total" @change="fetchOrderList" />
   </div>
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue';
-import { PageTitle, SearchBar, TablePagination } from '@/components';
-import { BILL_STATUS_OPTIONS, INVOICE_STATUS_OPTIONS, PAY_STATUS_OPTIONS } from '@/constants/status';
+import { reactive, ref, computed } from 'vue';
+import { PageTitle, TablePagination } from '@/components';
+import { getOrderList, getOrderProducts } from '@/api/pc/enterprise/order';
 import { getOrderStatusClass } from '@/utils/status';
+import { formatDate } from '@/utils';
+import type { ComponentInternalInstance } from 'vue';
+import { getCurrentInstance, toRefs } from 'vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { order_status } = toRefs<any>(proxy?.useDict('order_status'));
+
+const orderStatusOptions = computed(() => [{ label: '全部', value: '' }, ...(order_status.value || [])]);
+
+const loading = ref(false);
+const tableData = ref<any[]>([]);
 
 const searchForm = reactive({
   keyword: '',
-  dateRange: [],
-  billStatus: '',
-  invoiceStatus: '',
-  payStatus: ''
+  dateRange: [] as string[],
+  orderStatus: ''
 });
 
-const filters = [
-  { field: 'billStatus', label: '对账状态', options: BILL_STATUS_OPTIONS },
-  { field: 'invoiceStatus', label: '开票状态', options: INVOICE_STATUS_OPTIONS },
-  { field: 'payStatus', label: '支付状态', options: PAY_STATUS_OPTIONS }
-];
-
-const pagination = reactive({ page: 1, pageSize: 10, total: 100 });
-
-const tableData = ref([
-  {
-    orderDate: '2025-11-22',
-    orderNo: '2323232323',
-    productName: '清华同方超...',
-    quantity: 2,
-    price: 2115.97,
-    amount: 2115.97,
-    shippedQty: 2,
-    unshippedQty: 2,
-    status: '运输中'
-  },
-  {
-    orderDate: '2025-12-09',
-    orderNo: '2323412123',
-    productName: '清华同方超...',
-    quantity: 1,
-    price: 4476.12,
-    amount: 4476.12,
-    shippedQty: 1,
-    unshippedQty: 1,
-    status: '待发货'
-  },
-  {
-    orderDate: '2025-11-21',
-    orderNo: '4566784543',
-    productName: '越E500台式...',
-    quantity: 5,
-    price: 7751.34,
-    amount: 7751.34,
-    shippedQty: 5,
-    unshippedQty: 5,
-    status: '已收货'
-  },
-  {
-    orderDate: '2025-11-27',
-    orderNo: '2356565654',
-    productName: '清华同机电...',
-    quantity: 2,
-    price: 7936.37,
-    amount: 7936.37,
-    shippedQty: 2,
-    unshippedQty: 2,
-    status: '运输中'
-  },
-  {
-    orderDate: '2025-12-11',
-    orderNo: '2323412123',
-    productName: '清华同方超...',
-    quantity: 1,
-    price: 3931.45,
-    amount: 3931.45,
-    shippedQty: 1,
-    unshippedQty: 1,
-    status: '待发货'
-  },
-  {
-    orderDate: '2025-11-27',
-    orderNo: '2323232323',
-    productName: '清00台式机...',
-    quantity: 6,
-    price: 6132.75,
-    amount: 6132.75,
-    shippedQty: 6,
-    unshippedQty: 6,
-    status: '已收货'
-  },
-  {
-    orderDate: '2025-11-25',
-    orderNo: '2356565654',
-    productName: '机电脑超越...',
-    quantity: 7,
-    price: 3189.56,
-    amount: 3189.56,
-    shippedQty: 7,
-    unshippedQty: 7,
-    status: '已收货'
-  },
-  {
-    orderDate: '2025-11-20',
-    orderNo: '2323232323',
-    productName: '清华同方超...',
-    quantity: 2,
-    price: 993.66,
-    amount: 993.66,
-    shippedQty: 2,
-    unshippedQty: 2,
-    status: '运输中'
-  },
-  {
-    orderDate: '2025-11-17',
-    orderNo: '2323412123',
-    productName: '超越E500台...',
-    quantity: 1,
-    price: 2763.35,
-    amount: 2763.35,
-    shippedQty: 1,
-    unshippedQty: 1,
-    status: '待发货'
-  },
-  {
-    orderDate: '2025-12-05',
-    orderNo: '2323232323',
-    productName: '机电脑超越...',
-    quantity: 5,
-    price: 9286.45,
-    amount: 9286.45,
-    shippedQty: 5,
-    unshippedQty: 5,
-    status: '已收货'
+const pagination = reactive({ page: 1, pageSize: 10, total: 0 });
+
+const getStatusLabel = (value: string) => {
+  const found = order_status.value?.find((d: any) => d.value === value);
+  return found?.label || value;
+};
+
+const fetchOrderList = async () => {
+  loading.value = true;
+  try {
+    const params: any = {
+      pageNum: pagination.page,
+      pageSize: pagination.pageSize
+    };
+
+    if (searchForm.keyword) params.orderNo = searchForm.keyword;
+    if (searchForm.orderStatus) params.orderStatus = searchForm.orderStatus;
+    if (searchForm.dateRange && searchForm.dateRange.length === 2) {
+      params.beginTime = formatDate(searchForm.dateRange[0]);
+      params.endTime = formatDate(searchForm.dateRange[1]);
+    }
+
+    const res = await getOrderList(params);
+    if (res.code !== 200) return;
+
+    const orders = res.rows || [];
+    pagination.total = res.total || 0;
+
+    if (orders.length === 0) {
+      tableData.value = [];
+      return;
+    }
+
+    // 获取订单商品
+    const orderProductsMap: Record<number, any[]> = {};
+    const orderIds = orders.map((o: any) => o.id);
+    const prodRes = await getOrderProducts(orderIds);
+    if (prodRes.code === 200 && prodRes.rows) {
+      prodRes.rows.forEach((p: any) => {
+        if (!orderProductsMap[p.orderId]) {
+          orderProductsMap[p.orderId] = [];
+        }
+        orderProductsMap[p.orderId].push(p);
+      });
+    }
+
+    // 按订单合并:同一订单的商品名称拼接,数量/金额汇总
+    const rows: any[] = [];
+    orders.forEach((order: any) => {
+      const products = orderProductsMap[order.id] || [];
+      const productNames = products
+        .map((p: any) => p.productName || '')
+        .filter(Boolean)
+        .join('、');
+      const totalQty = products.reduce((sum: number, p: any) => sum + (p.orderQuantity || 0), 0);
+      const totalAmount = products.reduce((sum: number, p: any) => sum + (p.totalPrice || (p.unitPrice || 0) * (p.orderQuantity || 0)), 0);
+      const totalShipped = products.reduce((sum: number, p: any) => sum + (p.deliveredQuantity || 0), 0);
+      const totalUnshipped = totalQty - totalShipped;
+      const unitPrices = [...new Set(products.map((p: any) => p.unitPrice).filter(Boolean))];
+      const priceDisplay = unitPrices.length === 1 ? `¥${unitPrices[0].toLocaleString()}` : '—';
+
+      rows.push({
+        orderDate: order.createTime || '',
+        orderNo: order.orderNo || '',
+        productName: productNames || '',
+        quantity: totalQty,
+        price: priceDisplay,
+        amount: totalAmount || order.totalAmount || 0,
+        shippedQty: totalShipped,
+        unshippedQty: totalUnshipped,
+        status: getStatusLabel(order.orderStatus || '')
+      });
+    });
+
+    tableData.value = rows;
+  } catch (e) {
+    console.error('获取订单列表失败:', e);
+  } finally {
+    loading.value = false;
   }
-]);
+};
+
+const handleSearch = () => {
+  pagination.page = 1;
+  fetchOrderList();
+};
+
+const handleReset = () => {
+  searchForm.keyword = '';
+  searchForm.dateRange = [];
+  searchForm.orderStatus = '';
+  handleSearch();
+};
+
+fetchOrderList();
 </script>
+
+<style scoped lang="scss">
+.filter-bar {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 16px;
+}
+.product-name-text {
+  word-break: break-all;
+  line-height: 1.5;
+}
+</style>