编辑 | blame | 历史 | 原始文档

CRM 销售全流程追溯 - 前端文档

概述

本次改造实现销售全流程追溯功能,可从商机、合同、客户等多个维度查看完整的销售流程信息,包括商机 -> 合同 -> 销售订单 -> 出库 -> 回款 -> 退货的全链路数据。


一、改造清单

后端改造(已完成)

文件 改动说明
CrmSaleTraceApi.java 销售追溯 API 接口
CrmSaleTraceRespDTO.java 销售追溯响应 DTO
CrmSaleTraceApiImpl.java 销售追溯 API 实现
CrmBusinessDO.java 新增 orderId、orderNo 字段
CrmBusinessRespVO.java 新增 orderId、orderNo 字段
CrmContractMapper.java 新增 selectByBusinessId、selectListByCustomerId 方法
CrmReceivableService.java 新增 getReceivablePriceByContractId 方法

二、涉及页面

CRM 模块

页面 路径 改动说明
商机详情 /crm/business/detail 添加销售流程追溯入口
合同详情 /crm/contract/detail 添加销售流程追溯入口
客户详情 /crm/customer/detail 添加销售流程追溯列表

新增页面

页面 路径 说明
销售追溯详情 /crm/sale-trace/detail 销售流程追溯详情页

三、API 接口

CRM 销售追溯 API

方法 路径 说明
GET /rpc-api/crm/sale-trace/get-by-business-id 根据商机编号获取追溯信息
GET /rpc-api/crm/sale-trace/get-by-contract-id 根据合同编号获取追溯信息
GET /rpc-api/crm/sale-trace/get-list-by-customer-id 根据客户编号获取追溯列表

响应示例:

{
  "code": 0,
  "data": {
    "businessId": 1,
    "businessName": "商机A",
    "businessPrice": 100000,
    "businessStatus": "进行中",
    "businessCreateTime": "2024-01-01 10:00:00",
    "customerId": 1,
    "customerName": "客户A",
    "contractId": 1,
    "contractName": "合同A",
    "contractPrice": 95000,
    "contractStatus": 20,
    "contractCreateTime": "2024-01-15 10:00:00",
    "orderId": 1,
    "orderNo": "SO001",
    "orderPrice": 95000,
    "orderStatus": 0,
    "orderCreateTime": "2024-01-16 10:00:00",
    "receivablePrice": 50000,
    "receivableItems": [
      {
        "receivableId": 1,
        "receivableNo": "RK001",
        "price": 30000,
        "receivableTime": "2024-02-01"
      },
      {
        "receivableId": 2,
        "receivableNo": "RK002",
        "price": 20000,
        "receivableTime": "2024-03-01"
      }
    ]
  }
}

四、前端修改点

4.1 商机详情页面

文件路径: /src/views/crm/business/detail.vue

新增组件:

<!-- 销售流程追溯卡片 -->
<el-card class="sale-trace-card" header="销售流程追溯">
  <div v-if="traceInfo">
    <!-- 流程时间线 -->
    <el-timeline>
      <el-timeline-item
        :timestamp="traceInfo.businessCreateTime"
        placement="top"
        color="#409EFF"
      >
        <el-card>
          <h4>商机创建</h4>
          <p>商机名称:{{ traceInfo.businessName }}</p>
          <p>商机金额:¥{{ traceInfo.businessPrice }}</p>
        </el-card>
      </el-timeline-item>

      <el-timeline-item
        v-if="traceInfo.contractId"
        :timestamp="traceInfo.contractCreateTime"
        placement="top"
        color="#67C23A"
      >
        <el-card>
          <h4>合同签订</h4>
          <p>合同名称:{{ traceInfo.contractName }}</p>
          <p>合同金额:¥{{ traceInfo.contractPrice }}</p>
          <p>合同状态:{{ getContractStatusText(traceInfo.contractStatus) }}</p>
        </el-card>
      </el-timeline-item>

      <el-timeline-item
        v-if="traceInfo.orderId"
        :timestamp="traceInfo.orderCreateTime"
        placement="top"
        color="#E6A23C"
      >
        <el-card>
          <h4>销售订单</h4>
          <p>订单号:<el-link type="primary" @click="viewOrder(traceInfo.orderId)">{{ traceInfo.orderNo }}</el-link></p>
          <p>订单金额:¥{{ traceInfo.orderPrice }}</p>
          <p>订单状态:{{ getOrderStatusText(traceInfo.orderStatus) }}</p>
        </el-card>
      </el-timeline-item>

      <el-timeline-item
        v-if="traceInfo.receivablePrice > 0"
        placement="top"
        color="#F56C6C"
      >
        <el-card>
          <h4>回款记录</h4>
          <p>已回款金额:¥{{ traceInfo.receivablePrice }}</p>
          <el-table v-if="traceInfo.receivableItems" :data="traceInfo.receivableItems" size="small">
            <el-table-column prop="receivableNo" label="回款单号" />
            <el-table-column prop="price" label="回款金额" />
            <el-table-column prop="receivableTime" label="回款时间" />
          </el-table>
        </el-card>
      </el-timeline-item>
    </el-timeline>
  </div>
  <div v-else>
    <el-empty description="暂无销售流程数据" />
  </div>
</el-card>

新增方法:

import { getTraceByBusinessId } from '@/api/crm/saleTrace'

data() {
  return {
    traceInfo: null
  }
},

methods: {
  async loadTraceInfo() {
    const { data } = await getTraceByBusinessId(this.businessId)
    this.traceInfo = data
  },
  getContractStatusText(status) {
    const statusMap = { 0: '草稿', 10: '审批中', 20: '审批通过', 30: '审批不通过' }
    return statusMap[status] || '未知'
  },
  getOrderStatusText(status) {
    const statusMap = { 0: '未审核', 10: '已审核' }
    return statusMap[status] || '未知'
  },
  viewOrder(orderId) {
    this.$router.push({ path: '/erp/sale/order/detail', query: { id: orderId } })
  }
}

4.2 合同详情页面

文件路径: /src/views/crm/contract/detail.vue

新增方法:

import { getTraceByContractId } from '@/api/crm/saleTrace'

methods: {
  async loadTraceInfo() {
    const { data } = await getTraceByContractId(this.contractId)
    this.traceInfo = data
  }
}

4.3 客户详情页面

文件路径: /src/views/crm/customer/detail.vue

新增列表:

<el-card header="销售流程追溯">
  <el-table :data="traceList" size="small">
    <el-table-column prop="businessName" label="商机名称">
      <template #default="{ row }">
        <el-link v-if="row.businessId" type="primary" @click="viewBusiness(row.businessId)">
          {{ row.businessName }}
        </el-link>
        <span v-else>-</span>
      </template>
    </el-table-column>
    <el-table-column prop="contractName" label="合同名称">
      <template #default="{ row }">
        <el-link v-if="row.contractId" type="primary" @click="viewContract(row.contractId)">
          {{ row.contractName }}
        </el-link>
        <span v-else>-</span>
      </template>
    </el-table-column>
    <el-table-column prop="contractPrice" label="合同金额" width="120" />
    <el-table-column prop="orderNo" label="销售订单">
      <template #default="{ row }">
        <el-link v-if="row.orderId" type="primary" @click="viewOrder(row.orderId)">
          {{ row.orderNo }}
        </el-link>
        <span v-else>-</span>
      </template>
    </el-table-column>
    <el-table-column prop="receivablePrice" label="已回款" width="120" />
    <el-table-column label="回款率" width="100">
      <template #default="{ row }">
        <span v-if="row.contractPrice > 0">
          {{ ((row.receivablePrice / row.contractPrice) * 100).toFixed(1) }}%
        </span>
        <span v-else>-</span>
      </template>
    </el-table-column>
  </el-table>
</el-card>

4.4 API 定义

文件路径: /src/api/crm/saleTrace.js

import request from '@/utils/request'

// 根据商机编号获取销售流程追溯信息
export function getTraceByBusinessId(businessId) {
  return request({
    url: '/crm/sale-trace/get-by-business-id',
    method: 'get',
    params: { businessId }
  })
}

// 根据合同编号获取销售流程追溯信息
export function getTraceByContractId(contractId) {
  return request({
    url: '/crm/sale-trace/get-by-contract-id',
    method: 'get',
    params: { contractId }
  })
}

// 根据客户编号获取销售流程追溯列表
export function getTraceListByCustomerId(customerId) {
  return request({
    url: '/crm/sale-trace/get-list-by-customer-id',
    method: 'get',
    params: { customerId }
  })
}

五、数据模型变更

CRM 商机表变更

ALTER TABLE `crm_business`
ADD COLUMN `order_id` bigint DEFAULT NULL COMMENT '关联 ERP 销售订单编号' AFTER `total_price`,
ADD COLUMN `order_no` varchar(32) DEFAULT NULL COMMENT 'ERP 销售订单单号' AFTER `order_id`;

六、业务流程图

商机创建 → 商机跟进 → 合同签订 → 合同审批 → 生成订单 → 订单审批 → 销售出库 → 客户回款
    ↓           ↓          ↓          ↓          ↓          ↓          ↓          ↓
  10:00      12:00     15:00      16:00      17:00      18:00      20:00      25:00
                               
                            (金额变化追踪)
商机金额(100K) → 合同金额(95K) → 订单金额(95K) → 出库金额(95K) → 回款金额(50K)

七、注意事项

  1. 数据关联:追溯数据来源于多个模块(CRM、ERP),需确保数据一致性
  2. 权限控制:追溯查询需要对应模块的查看权限
  3. 数据完整性:部分业务可能只到某个环节(如只签合同未生成订单)
  4. 时间线展示:按时间顺序展示各环节信息
  5. 金额汇总:展示每个环节的金额变化情况

八、后续计划

Phase 内容 状态
Phase 1 客户数据统一 - ERP 使用 CRM 客户 已完成
Phase 2 产品数据统一 - CRM/ERP 使用 MDM 物料 已完成
Phase 3 合同生成销售订单 已完成
Phase 4 销售全流程追溯 已完成
Phase 5 营销数据报表 待开始
Phase 6 数据迁移与清理 待开始