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

Laravel+Inertia+Vue SPA中后台任务执行时的前端加载状态设计

最优设计方案:Laravel+Inertia+Vue 投资组合重算状态同步

一、状态管理:用Pinia统一维护任务状态

必须用Pinia,多个组件需要共享投资组合的重算状态(比如表格行、操作按钮),Pinia的全局存储能保证状态一致,避免各组件单独维护状态导致的不同步问题。

  • 定义portfolioStore,存储每个投资组合的任务状态:{ [portfolioId]: 'pending' | 'processing' | 'completed' | 'failed' }
  • 提供统一的状态更新action,比如updateTaskStatus(portfolioId, status),所有组件通过这个action修改状态,保证单一数据源。

二、任务状态监控:定时轮询是当前场景最优解

你的任务执行时间在1-2分钟,轮询实现简单、对前端技术要求低,适合UX能力较弱的场景,无需引入复杂的WebSocket广播(广播虽高效,但配置成本高)。

  • 轮询间隔设为10-15秒,平衡实时性和服务器压力
  • 后端提供接口/api/portfolios/{id}/task-status,返回对应投资组合的重算状态:
    • 建议在portfolios表新增calculation_status字段(枚举类型:idle/processing/completed/failed),或者单独建portfolio_calculation_tasks表记录任务ID和状态
    • Job执行时更新状态为processing,完成后设为completed,失败则设为failed

三、状态同步完整流程

  1. 触发重算:

    • 前端按钮点击后,调用Axios请求后端控制器,后端触发Job并立即返回成功(Job异步执行)
    • 同时调用Pinia的updateTaskStatus,将对应portfolio状态设为processing,渲染加载图标
  2. 轮询监控:

    • 触发重算后启动定时器,每隔10秒调用后端状态接口
    • 拿到状态后更新Pinia中的对应状态:
      • 状态为completed时,渲染对勾图标并清除定时器
      • 状态为failed时,显示错误提示并清除定时器
  3. 多组件同步:

    • 所有需要显示状态的组件(表格行、操作按钮)都从Pinia读取对应portfolio的状态,无需各自维护状态或单独发请求

四、简化版代码示例

后端(Laravel)

  1. 给portfolios表加状态字段:
Schema::table('portfolios', function (Blueprint $table) {
    $table->enum('calculation_status', ['idle', 'processing', 'completed', 'failed'])->default('idle');
});
  1. 控制器逻辑:
public function triggerRecalculate(Portfolio $portfolio)
{
    $portfolio->update(['calculation_status' => 'processing']);
    RecalculatePortfolioJob::dispatch($portfolio);
    return response()->json(['message' => '重算任务已启动']);
}

public function getTaskStatus(Portfolio $portfolio)
{
    return response()->json(['status' => $portfolio->calculation_status]);
}
  1. Job执行逻辑:
class RecalculatePortfolioJob implements ShouldQueue
{
    public function __construct(public Portfolio $portfolio) {}

    public function handle()
    {
        try {
            // 执行投资组合重算逻辑
            $this->portfolio->update(['calculation_status' => 'completed']);
        } catch (\Exception $e) {
            $this->portfolio->update(['calculation_status' => 'failed']);
            throw $e;
        }
    }
}

前端(Vue+Pinia)

  1. Pinia Store:
import { defineStore } from 'pinia'

export const usePortfolioStore = defineStore('portfolio', {
  state: () => ({
    taskStatuses: {} // 存储各portfolio的实时任务状态
  }),
  actions: {
    updateTaskStatus(portfolioId, status) {
      this.taskStatuses[portfolioId] = status
    },
    clearTaskStatus(portfolioId) {
      delete this.taskStatuses[portfolioId]
    }
  }
})
  1. 重算按钮组件:
<script setup>
import { usePortfolioStore } from '@/stores/portfolio'
import axios from 'axios'

const props = defineProps({
  portfolioId: Number
})

const store = usePortfolioStore()
let pollTimer = null

const triggerRecalculate = async () => {
  try {
    await axios.post(`/portfolios/${props.portfolioId}/recalculate`)
    store.updateTaskStatus(props.portfolioId, 'processing')
    
    // 启动轮询
    pollTimer = setInterval(async () => {
      const res = await axios.get(`/portfolios/${props.portfolioId}/task-status`)
      const status = res.data.status
      store.updateTaskStatus(props.portfolioId, status)
      
      if (status === 'completed' || status === 'failed') {
        clearInterval(pollTimer)
        if (status === 'failed') alert('重算失败,请稍后重试')
      }
    }, 10000)
  } catch (e) {
    console.error('触发重算失败:', e)
  }
}
</script>

<template>
  <button 
    @click="triggerRecalculate" 
    :disabled="store.taskStatuses[portfolioId] === 'processing'"
  >
    {{ store.taskStatuses[portfolioId] === 'processing' ? '重算中...' : '立即重算' }}
  </button>
</template>
  1. 表格行状态组件:
<script setup>
import { usePortfolioStore } from '@/stores/portfolio'

const props = defineProps({
  portfolio: Object
})

const store = usePortfolioStore()
// 优先取Pinia中的实时状态,无则取后端持久化状态
const currentStatus = store.taskStatuses[portfolio.id] || portfolio.calculation_status
</script>

<template>
  <tr>
    <td>{{ portfolio.name }}</td>
    <td>
      <span v-if="currentStatus === 'processing'">
        <i class="loading-icon"></i> 重算中
      </span>
      <span v-else-if="currentStatus === 'completed'">
        <i class="check-icon"></i> 已完成
      </span>
      <span v-else-if="currentStatus === 'failed'">
        <i class="error-icon"></i> 重算失败
      </span>
      <span v-else>
        <i class="idle-icon"></i> 已就绪
      </span>
    </td>
  </tr>
</template>

五、额外优化建议

  • 页面刷新时,从后端拉取所有投资组合的状态,初始化Pinia的taskStatuses,避免状态丢失
  • 给轮询加最大次数限制(比如12次,对应2分钟),超时后标记为失败,防止无限轮询
  • 按钮添加disabled状态,避免用户重复点击触发多次任务

内容的提问来源于stack exchange,提问作者Canelo Digital

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:53:26