Vue中如何基于数据库条件设置动态class并解决提前渲染问题?
问题与解决方案
问题
我有多个div元素,需要根据数据库返回的特定条件设置对应的class。但组件会在获取到数据库数据之前就完成渲染,尝试用async/await写异步函数处理,但没生效(我对JavaScript和Vue不太熟)。
原组件代码
<v-table v-if="allActions.length != 0"> <thead> <tr> <th class="text-center text-blue" :colspan="lastWeekCount + 1">Недели</th> </tr> <tr> <th class="text-left text-blue">Actions</th> <th v-for="week in 15" :key="week" class="text-center text-blue"> {{ week + firstWeekCount - 1 }} </th> </tr> </thead> <tbody> <tr v-for="action in allActions" :key="action"> <td>{{ action.reason }}</td> <td v-for="week in 15" :key="week" class="text-center"> <div> <v-dialog max-width="500"> <template v-slot:activator="{ props: activatorProps, }" > <div v-if="checkActionStatus(week + firstWeekCount - 1, action.id)" @click="selectChosenAction(week + firstWeekCount - 1, action.id)" v-bind="activatorProps" :class="checkActionStatus(week + firstWeekCount - 1, action.id)" style="height: 15px; width: 15px" ></div> </template> <template v-slot:default="{ isActive }"> <v-card title="Change a status"> <v-card-text> <v-select v-model="chosenAction.status" :items="['OK', 'nOK', 'Empty']" label="Status state" :disabled="isStatus" item-title="name" return-object variant="solo" ></v-select> <div v-if="isStatus" class="text-red-lighten-1"> Action is already chosen </div> </v-card-text> <v-card-actions> <v-spacer></v-spacer> <v-btn text="Close" @click="isActive.value = false"></v-btn> <v-btn class="ma-2" color="success" variant="flat" v-bind="props" @click="updateChosenAction" >Save</v-btn > </v-card-actions> </v-card> </template> </v-dialog> </div> </td> </tr> </tbody> </v-table>
原异步函数代码
const checkActionStatus = async (week, action_id) => { try { const response = await httpServer.post("selectActionByWeek", { week: week, action: action_id }); if (response.data[0]) { switch (response.data[0].status) { case 'Empty': return "bg-blue-grey-lighten-3 rounded-circle mx-auto"; case 'OK': return "bg-light-green-accent-3 rounded-circle mx-auto"; case 'nOK': return "bg-amber-lighten-2 rounded-circle mx-auto"; default: return "bg-blue-grey-lighten-3 rounded-circle mx-auto"; } } else { return "bg-blue-grey-lighten-3 rounded-circle mx-auto"; } } catch (error) { console.log(error); return "bg-blue-grey-lighten-3 rounded-circle mx-auto"; } };
问题原因
- 模板直接调用async函数会返回Promise,Vue模板无法解析Promise的最终结果,导致无法正确渲染class。
- 模板中两次调用
checkActionStatus会触发重复请求,浪费服务器资源。 - 组件渲染时数据库数据尚未获取完成,初始状态没有正确的class。
解决方案
步骤1:批量预加载所有状态数据
在组件初始化时,提前获取所有action对应每周的状态,存储在本地响应式变量中,避免模板中重复请求。
步骤2:改用同步函数获取状态
从本地存储的变量中同步读取状态,直接返回对应的class字符串,供模板使用。
步骤3:添加加载状态
在数据加载完成前显示加载提示,提升用户体验。
修改后的代码
脚本部分
import { ref, onMounted } from 'vue'; // 存储每个action对应每周的状态:{ "actionId-week": "OK/nOK/Empty" } const actionStatusMap = ref({}); // 加载状态标记 const isLoading = ref(true); // 批量获取所有action的周状态 const fetchAllActionStatuses = async () => { try { // 生成所有需要请求的Promise const requestPromises = allActions.value.flatMap(action => { return Array.from({ length: 15 }, (_, idx) => { const week = idx + 1 + firstWeekCount.value - 1; return httpServer.post("selectActionByWeek", { week, action: action.id }) .then(res => { const status = res.data[0]?.status || 'Empty'; actionStatusMap.value[`${action.id}-${week}`] = status; }); }); }); // 等待所有请求完成 await Promise.all(requestPromises); } catch (error) { console.error('加载状态失败:', error); } finally { isLoading.value = false; } }; // 组件挂载后加载数据 onMounted(async () => { // 假设这里已经获取了allActions数据,若未获取需先调用对应接口 // await fetchAllActions(); await fetchAllActionStatuses(); }); // 同步获取状态对应的class const checkActionStatus = (week, action_id) => { const status = actionStatusMap.value[`${action_id}-${week}`] || 'Empty'; switch (status) { case 'Empty': return "bg-blue-grey-lighten-3 rounded-circle mx-auto"; case 'OK': return "bg-light-green-accent-3 rounded-circle mx-auto"; case 'nOK': return "bg-amber-lighten-2 rounded-circle mx-auto"; default: return "bg-blue-grey-lighten-3 rounded-circle mx-auto"; } };
模板部分
<template> <!-- 加载状态 --> <div v-if="isLoading" class="text-center py-4">加载中...</div> <v-table v-else-if="allActions.length !== 0"> <thead> <tr> <th class="text-center text-blue" :colspan="lastWeekCount + 1">Недели</th> </tr> <tr> <th class="text-left text-blue">Actions</th> <th v-for="week in 15" :key="week" class="text-center text-blue"> {{ week + firstWeekCount - 1 }} </th> </tr> </thead> <tbody> <!-- 用action.id作为key更稳定 --> <tr v-for="action in allActions" :key="action.id"> <td>{{ action.reason }}</td> <td v-for="week in 15" :key="week" class="text-center"> <div> <v-dialog max-width="500"> <template v-slot:activator="{ props: activatorProps }"> <div @click="selectChosenAction(week + firstWeekCount - 1, action.id)" v-bind="activatorProps" :class="checkActionStatus(week + firstWeekCount - 1, action.id)" style="height: 15px; width: 15px" ></div> </template> <template v-slot:default="{ isActive }"> <v-card title="Change a status"> <v-card-text> <v-select v-model="chosenAction.status" :items="['OK', 'nOK', 'Empty']" label="Status state" :disabled="isStatus" item-title="name" return-object variant="solo" ></v-select> <div v-if="isStatus" class="text-red-lighten-1"> Action is already chosen </div> </v-card-text> <v-card-actions> <v-spacer></v-spacer> <v-btn text="Close" @click="isActive.value = false"></v-btn> <v-btn class="ma-2" color="success" variant="flat" @click="updateChosenAction" >Save</v-btn > </v-card-actions> </v-card> </template> </v-dialog> </div> </td> </tr> </tbody> </v-table> </template>
额外优化建议
- 建议后端提供批量查询接口,一次性获取所有action的周状态,减少请求次数。
- 点击Save按钮更新状态后,同步修改
actionStatusMap中的对应值,无需刷新页面即可显示最新状态。
内容的提问来源于stack exchange,提问作者Nerdy
相关产品推荐
相关产品推荐

