You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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";
  }
};

问题原因

  1. 模板直接调用async函数会返回Promise,Vue模板无法解析Promise的最终结果,导致无法正确渲染class。
  2. 模板中两次调用checkActionStatus会触发重复请求,浪费服务器资源。
  3. 组件渲染时数据库数据尚未获取完成,初始状态没有正确的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 23:55:54