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

CRM 跟进记录接口 - 前端修改说明

问题描述

调用 /admin-api/crm/follow-up-record/create 接口时出现 JSON 解析错误:

Cannot deserialize value of type `java.util.ArrayList<java.lang.String>` from String value

原因分析

后端字段 picUrlsfileUrls 定义为 List<String> 类型,期望接收数组格式的数据,但前端传递的是字符串格式。

当前前端发送的数据(错误)

{
  "bizId": 2,
  "bizType": 1,
  "type": 2,
  "nextTime": "1783924981689",
  "content": "4123123",
  "picUrls": "http://127.0.0.1:48080/admin-api/infra/file/29/get/xxx.png",
  "fileUrls": "http://127.0.0.1:48080/admin-api/infra/file/29/get/xxx.png"
}

后端期望的数据(正确)

{
  "bizId": 2,
  "bizType": 1,
  "type": 2,
  "nextTime": "1783924981689",
  "content": "4123123",
  "picUrls": ["http://127.0.0.1:48080/admin-api/infra/file/29/get/xxx.png"],
  "fileUrls": ["http://127.0.0.1:48080/admin-api/infra/file/29/get/xxx.png"]
}

修改方案

涉及页面

页面 路径 说明
商机详情 /crm/business/detail 添加跟进记录
客户详情 /crm/customer/detail 添加跟进记录
合同详情 /crm/contract/detail 添加跟进记录

修改点

1. API 调用参数处理

在调用创建跟进记录接口前,将 URL 字符串转为数组:

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

import request from '@/utils/request'

/**
 * 创建跟进记录
 */
export function createFollowUpRecord(data) {
  // 处理 picUrls 和 fileUrls:确保是数组格式
  const processedData = {
    ...data,
    picUrls: Array.isArray(data.picUrls) ? data.picUrls : (data.picUrls ? [data.picUrls] : []),
    fileUrls: Array.isArray(data.fileUrls) ? data.fileUrls : (data.fileUrls ? [data.fileUrls] : [])
  }
  
  return request({
    url: '/crm/follow-up-record/create',
    method: 'post',
    data: processedData
  })
}

2. 表单提交数据处理

在提交表单时,确保数据格式正确:

// 示例:商机详情页添加跟进记录
methods: {
  async submitFollowUp() {
    const formData = {
      bizId: this.businessId,
      bizType: 1,
      type: this.form.type,
      nextTime: this.form.nextTime,
      content: this.form.content,
      // 确保 picUrls 和 fileUrls 是数组
      picUrls: this.form.picUrls || [],
      fileUrls: this.form.fileUrls || []
    }
    
    await createFollowUpRecord(formData)
  }
}

3. 图片/附件上传后的数据处理

上传成功后,直接存储为数组格式:

// 上传图片成功后
handleUploadSuccess(response) {
  if (!this.form.picUrls) {
    this.form.picUrls = []
  }
  this.form.picUrls.push(response.url)
}

// 删除图片时
handleRemoveImage(index) {
  this.form.picUrls.splice(index, 1)
}

完整示例

新增跟进记录弹窗组件

<template>
  <el-dialog v-model="visible" title="添加跟进记录" width="600px">
    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
      <el-form-item label="跟进类型" prop="type">
        <el-select v-model="form.type" placeholder="请选择跟进类型">
          <el-option label="电话跟进" :value="1" />
          <el-option label="拜访跟进" :value="2" />
          <el-option label="微信跟进" :value="3" />
        </el-select>
      </el-form-item>
      
      <el-form-item label="跟进内容" prop="content">
        <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请输入跟进内容" />
      </el-form-item>
      
      <el-form-item label="图片">
        <el-upload
          :action="uploadUrl"
          :on-success="handlePicSuccess"
          :on-remove="handlePicRemove"
          :file-list="picFileList"
          list-type="picture-card"
        >
          <el-icon><Plus /></el-icon>
        </el-upload>
      </el-form-item>
      
      <el-form-item label="附件">
        <el-upload
          :action="uploadUrl"
          :on-success="handleFileSuccess"
          :on-remove="handleFileRemove"
          :file-list="fileFileList"
        >
          <el-button>上传附件</el-button>
        </el-upload>
      </el-form-item>
      
      <el-form-item label="下次联系" prop="nextTime">
        <el-date-picker v-model="form.nextTime" type="datetime" placeholder="选择下次联系时间" />
      </el-form-item>
    </el-form>
    
    <template #footer>
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="submitForm">确定</el-button>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { createFollowUpRecord } from '@/api/crm/followUpRecord'

const visible = ref(false)
const formRef = ref()

const form = reactive({
  type: null,
  content: '',
  nextTime: null,
  picUrls: [],  // 确保初始化为数组
  fileUrls: []   // 确保初始化为数组
})

const rules = {
  type: [{ required: true, message: '请选择跟进类型', trigger: 'change' }],
  content: [{ required: true, message: '请输入跟进内容', trigger: 'blur' }],
  nextTime: [{ required: true, message: '请选择下次联系时间', trigger: 'change' }]
}

const picFileList = ref([])
const fileFileList = ref([])

// 图片上传成功
const handlePicSuccess = (response) => {
  if (!form.picUrls) form.picUrls = []
  form.picUrls.push(response.url)
  picFileList.value.push({ name: response.name, url: response.url })
}

// 图片移除
const handlePicRemove = (file) => {
  const index = picFileList.value.indexOf(file)
  if (index > -1) {
    picFileList.value.splice(index, 1)
    form.picUrls.splice(index, 1)
  }
}

// 附件上传成功
const handleFileSuccess = (response) => {
  if (!form.fileUrls) form.fileUrls = []
  form.fileUrls.push(response.url)
  fileFileList.value.push({ name: response.name, url: response.url })
}

// 附件移除
const handleFileRemove = (file) => {
  const index = fileFileList.value.indexOf(file)
  if (index > -1) {
    fileFileList.value.splice(index, 1)
    form.fileUrls.splice(index, 1)
  }
}

// 提交表单
const submitForm = async () => {
  await formRef.value.validate()
  await createFollowUpRecord({
    bizId: props.bizId,
    bizType: props.bizType,
    ...form
  })
  ElMessage.success('添加成功')
  visible.value = false
}

// 打开弹窗
const open = (bizId, bizType) => {
  visible.value = true
  form.bizId = bizId
  form.bizType = bizType
  form.picUrls = []
  form.fileUrls = []
  picFileList.value = []
  fileFileList.value = []
}

defineExpose({ open })
</script>

注意事项

  1. 初始化为空数组picUrlsfileUrls 字段在 data 中初始化为空数组 [],而不是空字符串 ''

  2. 上传后追加到数组:图片/附件上传成功后,使用 push() 方法追加到数组中

  3. 删除时从数组移除:删除图片/附件时,使用 splice() 方法从数组中移除对应项

  4. 提交前检查格式:确保传递给接口的数据中,picUrlsfileUrls 始终是数组格式


测试验证

修改完成后,请进行以下测试:

  1. 不上传图片和附件,直接提交 → 应成功
  2. 上传一张图片,提交 → 应成功
  3. 上传多张图片,提交 → 应成功
  4. 上传图片后删除,再提交 → 应成功

相关接口

方法 路径 说明
POST /admin-api/crm/follow-up-record/create 创建跟进记录
PUT /admin-api/crm/follow-up-record/update 更新跟进记录
DELETE /admin-api/crm/follow-up-record/delete 删除跟进记录
GET /admin-api/crm/follow-up-record/page 分页查询跟进记录