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

