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

Laravel密码重置链接无法跳转至Quasar/Vue SPA重置页面问题

问题描述

我用Quasar/Vue.js单页应用(SPA)搭配Laravel做后端,能正常请求并接收密码重置链接,但点击链接后会跳转到SPA首页,而非密码重置页面。

链接示例:http://localhost:3000/password-reset/376ecca99a228d274f22ea45de20d0d8d986c3ff5860def1b7cafd92cb5dba27?email=john.doe@somedomain.org

提供的代码文件:

router/index.js

import { route } from 'quasar/wrappers'
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router'
import routes from './routes'
import { useAuthStore } from "../stores/auth";

export default route(function (/* { store, ssrContext } */) {
  const createHistory = process.env.SERVER
    ? createMemoryHistory
    : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory)

  const Router = createRouter({
    scrollBehavior: () => ({ left: 0, top: 0 }),
    routes,

    history: createHistory(process.env.VUE_ROUTER_BASE)
  })

  Router.beforeEach(async (to) => {

    // redirect to login page if not logged in and trying to access a restricted page
    const publicPages = ['/login', '/register', ' / '];
    const authRequired = !publicPages.includes(to.path);
    if (/\/password-reset\/[a-w0-9\?@\.]+/.test(to.path)) { authRequired = false }
    const authStore = useAuthStore();
 
    
    const user = localStorage.getItem('user')
    
    if (authRequired && user === null && authStore.authUser === null) {
      return ({ path: '/login' })
    }
    //fetch authenticated user in back-end
    if (authRequired && user && !authStore.authUser) authStore.fetchUser()


  });

  return Router
})

router/routes.js

const routes = [
  {
    path: '/',
    component: () => import('layouts/MainLayout.vue'),
    children: [
      { path: '', component: () => import('pages/HomeView.vue') },
      { path: 'persons', component: () => import('pages/PersonsScrollView.vue') },
      { path: 'banks', component: () => import('pages/BanksView.vue') },
      { path: 'events', component: () => import('pages/EventsView.vue') },
      { path: 'general-expenses', component: () => import('pages/GeneralExpenses.vue') },
      { path: 'general-incomes', component: () => import('pages/GeneralIncomes.vue') },
      { path: 'cassette', component: () => import('pages/CassetteOperationView.vue') },
      { path: 'bank', component: () => import('pages/BankOperationView.vue') },
      { path: 'periods', component: () => import('pages/PeriodsView.vue') },
      { path: 'register', component: () => import('pages/RegisterView.vue') },
      { path: 'login', component: () => import('pages/LoginView.vue') },
      { path: 'forgot-password', component: () => import('pages/ForgotPasswordView.vue') },
      { path: 'password-reset/:token', component: () => import('pages/PasswordResetView.vue') },
      { path: 'about', component: () => import('pages/AboutView.vue') },
    ]
  },

  // Always leave this as last one,
  // but you can also remove it
  {
    path: '/:catchAll(.*)*',
    component: () => import('pages/ErrorNotFound.vue')
  }
]

export default routes

后续补充问题

  • EDIT 2:把quasar.config.js中的vueRouterMode从hash改成history后,跳转问题解决,但刷新页面会触发404错误,该怎么处理?
  • EDIT 3:发现域名后加#的链接能正常工作,请问如何让Laravel生成带#的密码重置链接?

解决方案

一、初始跳转问题(hash模式下链接失效)

hash模式下,Vue Router仅解析#后的路径,而Laravel生成的链接是不带#的普通路径,导致SPA无法识别路由,直接跳转到首页。另外你之前的正则判断存在漏洞:[a-w]漏掉了x-z,且query参数不在to.path中,所以匹配逻辑不生效。

如果暂时不想切换到history模式,可先修正路由守卫的判断逻辑:

// 替换原来的正则判断
if (to.path.startsWith('/password-reset/')) { 
  authRequired = false 
}

二、history模式下刷新404的解决办法

history模式依赖服务器配置,刷新时浏览器会直接请求对应路径的资源,而SPA只有一个index.html,需让服务器把所有路由请求转发到该文件。

1. 开发环境(Quasar dev)

在quasar.config.js中添加devServer配置:

module.exports = configure(function (/* ctx */) {
  return {
    // ...其他配置
    devServer: {
      historyApiFallback: true
    }
  }
})

2. 生产环境(Nginx为例)

修改Nginx配置文件,添加try_files指令:

location / {
  try_files $uri $uri/ /index.html;
}

3. 生产环境(Apache为例)

在项目根目录创建.htaccess文件:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>

三、让Laravel生成带#的密码重置链接

通过自定义密码重置通知类,修改链接生成逻辑:

步骤1:生成自定义通知类

执行Artisan命令:

php artisan make:notification CustomResetPasswordNotification

步骤2:修改通知类的toMail方法

在app/Notifications/CustomResetPasswordNotification.php中:

<?php

namespace App\Notifications;

use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class CustomResetPasswordNotification extends Notification
{
    public $token;

    public function __construct($token)
    {
        $this->token = $token;
    }

    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        // 拼接带#的链接,将路由路径放到#后
        $resetUrl = url('/') . '#/password-reset/' . $this->token . '?email=' . urlencode($notifiable->email);

        return (new MailMessage)
            ->line('您收到这封邮件是因为我们收到了您的账户密码重置请求。')
            ->action('重置密码', $resetUrl)
            ->line('如果您没有请求重置密码,请忽略此邮件。');
    }
}

步骤3:替换User模型中的密码重置通知

在app/Models/User.php中重写sendPasswordResetNotification方法:

<?php

namespace App\Models;

use App\Notifications\CustomResetPasswordNotification;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    // ...其他代码

    public function sendPasswordResetNotification($token)
    {
        $this->notify(new CustomResetPasswordNotification($token));
    }
}

修改后,Laravel生成的链接会变成http://localhost:3000/#/password-reset/xxx?email=xxx格式,适配hash模式的Vue Router。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 23:36:00