调用 /admin-api/crm/follow-up-record/create 接口时出现 JSON 解析错误:
Cannot deserialize value of type `java.util.ArrayList<java.lang.String>` from String value
后端字段 picUrls 和 fileUrls 定义为 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 |
添加跟进记录 |
在调用创建跟进记录接口前,将 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
})
}
在提交表单时,确保数据格式正确:
// 示例:商机详情页添加跟进记录
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)
}
}
上传成功后,直接存储为数组格式:
// 上传图片成功后
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>
初始化为空数组:picUrls 和 fileUrls 字段在 data 中初始化为空数组 [],而不是空字符串 ''
上传后追加到数组:图片/附件上传成功后,使用 push() 方法追加到数组中
删除时从数组移除:删除图片/附件时,使用 splice() 方法从数组中移除对应项
提交前检查格式:确保传递给接口的数据中,picUrls 和 fileUrls 始终是数组格式
修改完成后,请进行以下测试:
| 方法 | 路径 | 说明 |
|---|---|---|
| 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 | 分页查询跟进记录 |