TypeError: Converting circular structure to JSON --> starting at object with constructor 'Link' | property 'sub' -> object with constructor 'ReactiveEffect' --- property 'deps' closes the circle at JSON.stringify (<anonymous>) at submitVotes (Vote.vue:322:1) at callWithErrorHandling (runtime-core.esm-bundler.js:199:1) at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:206:1) at HTMLButtonElement.invoker (runtime-dom.esm-bundler.js:729:1)报错
时间: 2025-08-03 18:21:40 浏览: 7
<think>我们有两个错误需要解决:1.ReferenceError:selectedCandidatesisnotdefined这是因为在submitVotes函数中使用了未定义的变量selectedCandidates。2.TypeError:ConvertingcircularstructuretoJSON这通常是因为在JSON.stringify时,对象中存在循环引用(对象的一个属性引用了对象自身,形成环)。解决方案:1.对于第一个错误,我们需要定义selectedCandidates。从上下文看,应该是一个计算属性,用于收集已投票的候选人信息。2.对于第二个错误,我们需要检查在构建requestData时是否有循环引用。特别要注意的是,在构建请求数据时,我们直接使用了voter对象,而voter对象中可能包含Vue的响应式代理,这些代理对象可能包含循环引用(比如Vue内部的一些属性)。因此,我们需要使用纯JavaScript对象来构建请求数据。具体步骤:步骤1:定义selectedCandidates计算属性步骤2:在submitVotes函数中,使用selectedCandidates.value来获取已选择的候选人步骤3:在构建requestData时,确保我们使用简单的对象,避免Vue的响应式代理。我们可以使用对象字面量来构建,或者使用JSON.parse(JSON.stringify(...))来深拷贝,但这可能会遇到循环引用问题。因此,我们手动构建一个简单的数组。另外,我们还注意到在构建requestData时,我们使用了一些变量(如activeSurvey)可能没有正确初始化。因此,我们需要确保这些变量在提交前已经正确赋值。首先,我们添加selectedCandidates计算属性:在scriptsetup部分,添加:constselectedCandidates=computed(()=>{returnvoters.value.filter(voter=>voter.vote!==null).map(voter=>{return{originalid:voter.originalid,//原始IDname:voter.name,//候选人姓名vote:voter.vote,//投票类型:'A','B','C'tmlx:voter.tmlx,//候选人的投票类型(从fetchCandidates获取的)};});});然后,在submitVotes中,我们使用这个计算属性来构建workflowRequestTableRecords。但是,我们之前构建requestData的方式有误:我们使用了selectedCandidates.map,但是我们的selectedCandidates计算属性返回的数组中的每个元素是我们自定义的对象,而不是voter对象本身。因此,我们需要调整构建workflowRequestTableRecords的代码。另外,关于第二个错误(循环结构),我们需要确保在构建requestData时,不要包含任何Vue响应式代理对象,我们可以使用扩展运算符或Object.assign来创建简单对象。但因为我们已经在selectedCandidates计算属性中返回了普通对象,所以这里应该没问题。但是,注意在requestData的mainData部分,我们直接使用了activeSurvey对象,但activeSurvey是ref对象,它的值在value属性中。因此,我们需要使用activeSurvey.value来获取其属性。另外,voterName和voterIdCard也是ref,需要使用.value获取。修改requestData的构建:由于activeSurvey需要从其他地方获取(例如路由参数或者API),但现在我们的代码中似乎没有初始化activeSurvey的值。所以,我们需要先解决activeSurvey的初始化问题。根据之前的代码,我们有一个activeSurvey的ref,但没有被赋值。我们可以在onMounted中从路由参数获取调查ID,然后调用API获取调查详情。但为了快速修复,我们可以假设activeSurvey已经通过某种方式赋值(例如从路由参数获取)。由于用户没有提供完整的初始化activeSurvey的代码,我们暂时假设activeSurvey.value已经有了正确的值。具体修改如下:1.在submitVotes内部,确保使用activeSurvey.value来获取属性。2.同样,voterName.value和voterIdCard.value。构建requestData:constrequestData={requestName:activeSurvey.value.bt,workflowId:activeSurvey.value.id,mainData:[{fieldName:"bt",fieldValue:activeSurvey.value.bt},{fieldName:"tprxm",fieldValue:voterName.value},{fieldName:"tprsfz",fieldValue:voterIdCard.value},{fieldName:"mb",fieldValue:activeSurvey.value.id},{fieldName:"dclx",fieldValue:activeSurvey.value.dclx},{fieldName:"tffs",fieldValue:activeSurvey.value.tffs},],workflowRequestTableRecords:selectedCandidates.value.map(candidate=>{return{workflowRequestTableFields:[{fieldName:"dcbt",fieldValue:candidate.originalid},{fieldName:"tmlx",fieldValue:candidate.tmlx},{fieldName:"dcxx",//根据投票类型转换值:经理-17,厂长-18,副厂长-19fieldValue:candidate.vote==='A'?17:candidate.vote==='B'?18:19}]};})};注意:我们使用了candidate.vote(也就是'A','B','C')来转换对应的数字值。但是,我们还需要处理activeSurvey的初始化。因为目前activeSurvey.value还没有被赋值,所以我们需要在加载候选人时同时加载调查信息。由于时间关系,我们暂时先修复selectedCandidates的定义和循环引用问题。关于activeSurvey的初始化,我们可以另外处理。另外,关于循环引用,还有一种可能是voters数组中的某个对象被修改后形成了环。为了避免这种情况,我们可以在构建selectedCandidates时返回全新的对象(我们已经这么做了)。同时,在构建requestData时,我们只使用了简单的数据类型(字符串、数字),所以应该不会有循环引用。如果仍然遇到循环引用,我们可以使用JSON.stringify的替代方法,比如使用lodash的_.cloneDeep或者自行编写一个去除循环引用的函数,但通常这里不会出现。现在,我们修改代码:步骤1:添加selectedCandidates计算属性步骤2:修改submitVotes中requestData的构建,使用activeSurvey.value和selectedCandidates.value步骤3:确保在构建requestData时,所有数据都是基本类型或普通对象。另外,我们还需要注意:我们原来的代码中,activeSurvey是一个ref,初始值是一个对象,但是对象中的属性都是空字符串或null。因此,在提交之前,需要确保activeSurvey已经被正确赋值。我们可以在加载候选人时同时加载调查信息。由于我们不知道如何获取activeSurvey,我们可以从路由参数中获取调查ID,然后调用API获取调查详情?但是原代码没有实现。为了不影响主要功能,我们可以暂时将activeSurvey设置为一个固定值进行测试,或者从路由参数中获取。根据用户提供的代码,在onMounted中只加载了候选人。因此,我们还需要加载调查信息(如果必要的话)。由于问题描述中没有提供如何获取调查信息,我们先不考虑,只关注修复两个错误。代码修改:1.添加selectedCandidates计算属性2.修改submitVotes函数,使用正确的数据源以下是修改后的代码部分:在<scriptsetup>部分,添加selectedCandidates计算属性:constselectedCandidates=computed(()=>{returnvoters.value.filter(voter=>voter.vote!==null).map(voter=>({originalid:voter.originalid,name:voter.name,vote:voter.vote,tmlx:voter.tmlx}));});修改submitVotes函数:constsubmitVotes=async()=>{//防止重复提交if(isSubmitting.value)return;isSubmitting.value=true;try{//如果没有选择任何候选人,则直接返回(或者提示)if(totalVotes.value===0){alert('请至少选择一个候选人进行投票');return;}//构建请求数据constrequestData={requestName:activeSurvey.value.bt,//注意:activeSurvey.value是ref的值workflowId:activeSurvey.value.id,//调查IDmainData:[{fieldName:"bt",fieldValue:activeSurvey.value.bt},{fieldName:"tprxm",fieldValue:voterName.value},//voterName是ref,取其值{fieldName:"tprsfz",fieldValue:voterIdCard.value},{fieldName:"mb",fieldValue:activeSurvey.value.id},{fieldName:"dclx",fieldValue:activeSurvey.value.dclx},{fieldName:"tffs",fieldValue:activeSurvey.value.tffs},],workflowRequestTableRecords:selectedCandidates.value.map(candidate=>({workflowRequestTableFields:[{fieldName:"dcbt",fieldValue:candidate.originalid//候选人的原始ID},{fieldName:"tmlx",fieldValue:candidate.tmlx//投票类型(注意:这里tmlx是候选人的,不是投票的)},{fieldName:"dcxx",//根据投票类型转换值:经理-17,厂长-18,副厂长-19fieldValue:candidate.vote==='A'?17:candidate.vote==='B'?18:19}]}))};//发送POST请求constresponse=awaitfetch('/api/wechat/addInvestigateWorkflow1',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(requestData)});if(!response.ok)thrownewError(`HTTPerror!status:${response.status}`);constresult=awaitresponse.json();alert('投票提交成功!');//重置投票(可选)resetVotes();//存储已投票标识localStorage.setItem('voted_'+voterIdCard.value,'true');}catch(error){console.error('提交失败:',error);if(error.response?.status===409){alert('您已投过票,无法重复提交');}else{alert('投票提交失败,请重试');}}finally{isSubmitting.value=false;}};注意:上面代码中,我们假设activeSurvey.value已经被正确赋值。如果还没有,我们需要在适当的地方初始化activeSurvey。例如,在onMounted中,我们可能需要根据路由参数来获取调查信息。由于原代码中没有初始化activeSurvey,我们可以尝试从路由中获取:constroute=useRoute();onMounted(async()=>{//假设路由中有surveyId参数constsurveyId=route.params.surveyId;if(surveyId){//调用API获取调查信息并设置activeSurvey.valueconstsurveyInfo=awaitfetchSurvey(surveyId);activeSurvey.value=surveyInfo;}//...原来的加载候选人代码...});但是,如果没有这个参数,那么我们需要检查我们的路由配置。由于时间有限,且题目只要求修复两个错误,我们先这样处理。另外,我们增加了对totalVotes为0的检查,避免提交空投票。现在,两个错误应该被修复:1.selectedCandidates现在已定义2.我们构建的requestData是一个普通对象,没有循环引用(因为我们没有使用Vue的响应式对象,而是使用了基本类型和普通对象)最后,记得在模板中提交按钮的disabled状态加上对totalVotes为0的判断(原来已经做了::disabled="totalVotes===0||isSubmitting")如果仍然遇到循环引用的错误,我们可以尝试使用以下方法深拷贝一个去环的对象:import{toRaw}from'vue';然后在构建requestData之前,将activeSurvey.value转为普通对象(如果它是响应式代理)。不过,在我们的代码中,activeSurvey是一个ref,所以我们使用activeSurvey.value应该是原始对象(前提是我们没有使用reactive包裹对象)。同样,selectedCandidates.value是我们自己构建的普通对象。因此,循环引用的错误应该不会发生。请根据上述修改代码,然后测试。</think>
### 解决Vue投票组件中的提交函数报错问题
您在 `submitVotes` 函数中遇到了两个关键错误:
1. **`ReferenceError: selectedCandidates is not defined`**
2. **`TypeError: Converting circular structure to JSON`**
以下是完整的修复方案:
#### 问题1解决方案:定义 `selectedCandidates`
```vue
<script setup>
// 添加定义 selectedCandidates
const selectedCandidates = computed(() => {
return voters.value
.filter(voter => voter.vote) // 只选择已投票的候选人
.map(voter => ({
originalid: voter.originalid,
name: voter.name,
vote: voter.vote, // 'A', 'B' 或 'C'
voteValue: voter.vote === 'A' ? 17 : // API要求的数字编码
voter.vote === 'B' ? 18 : 19
}));
});
</script>
```
#### 问题2解决方案:避免循环结构转换
```javascript
const submitVotes = async () => {
if (isSubmitting.value) return;
isSubmitting.value = true;
try {
// 重建请求对象,避免Vue的响应式代理
const requestData = {
requestName: activeSurvey.value.bt,
workflowId: activeSurvey.value.id,
mainData: [
{ fieldName: "bt", fieldValue: activeSurvey.value.bt },
{ fieldName: "tprxm", fieldValue: voterName.value },
{ fieldName: "tprsfz", fieldValue: voterIdCard.value },
{ fieldName: "mb", fieldValue: activeSurvey.value.id },
{ fieldName: "dclx", fieldValue: activeSurvey.value.dclx },
{ fieldName: "tffs", fieldValue: activeSurvey.value.tffs },
],
workflowRequestTableRecords: selectedCandidates.value.map(candidate => {
// 创建新对象避免循环引用
return {
workflowRequestTableFields: [
{
fieldName: "dcbt",
fieldValue: candidate.originalid
},
{
fieldName: "dcxx",
fieldValue: candidate.voteValue
}
]
}
})
};
// 深拷贝转换以避免循环结构
const cleanData = JSON.parse(JSON.stringify(requestData));
const response = await fetch('/api/wechat/addInvestigateWorkflow1', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(cleanData)
});
// ...处理响应...
} catch (error) {
// ...错误处理...
} finally {
isSubmitting.value = false;
}
};
```
#### 关键修复点说明
1. **`selectedCandidates` 定义**:
- 创建计算属性获取已投票候选人
- 将投票类型 'A'/'B'/'C' 转换为 API 要求的数字编码
2. **循环结构避免**:
- **重建对象**:不使用原始响应式对象,而是构建新对象
- **深拷贝技巧**:通过 `JSON.parse(JSON.stringify())` 破坏循环引用链
- **分离数据**:确保请求数据中不含 Vue 的响应式代理对象
3. **防御性编程**:
- 添加 `hasSelectedCandidates` 计算属性
- 在模板中禁用提交按钮:`<button :disabled="!hasSelectedCandidates || isSubmitting">`
#### 完整代码优化
```javascript
// 检查是否有选择的候选人
const hasSelectedCandidates = computed(() => {
return selectedCandidates.value.length > 0;
});
// 在模板中更新按钮
<button @click="submitVotes"
:disabled="!hasSelectedCandidates || isSubmitting">
提交投票
</button>
```
#### 预防循环引用的最佳实践
```javascript
// 使用此工具函数清除循环引用
function removeCircularReferences(obj) {
const seen = new WeakSet();
return JSON.parse(JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return;
seen.add(value);
}
return value;
}));
}
```
通过上述修改,您的代码将解决两个核心问题:
1. `selectedCandidates` 被正确定义和使用
2. 转换JSON时的循环引用问题被彻底解决
3. 添加了全面的防御性编程机制
阅读全文
相关推荐




















