返回

解决 Laravel Jetstream 地址栏 GET 参数闪烁 (Vue)

vue.js

解决地址栏 GET 参数闪烁问题 (Laravel Jetstream + Vue)

在使用 Laravel Jetstream 和 Vue 构建的应用中,处理带有 GET 参数的 URL 时,你可能会遇到地址栏参数顺序闪烁的问题。比如,生成一个像这样的 URL:

http://localhost/admin/users/list?page=2&field=name&direction=asc

然后访问它,你会发现地址栏闪一下,参数顺序变了:

http://localhost/admin/users/list?direction=asc&field=name&page=2

挺烦人的,对吧?下面就来好好说说这个问题。

一、问题根源

这种闪烁现象通常是由于浏览器、前端框架 (Vue/Inertia) 或后端框架 (Laravel) 处理 URL 参数的顺序不一致导致的。让我们逐个排查:

  1. Laravel 的 withQueryString() : 这个方法将请求的查询参数附加到分页链接中。它并没有明确规定参数的顺序。
  2. 前端路由库 (Inertia.js/Vue Router) : 在构建 URL 时,前端路由的处理过程可能有其内部的排序逻辑。
  3. 浏览器行为 : 浏览器自身也可能对 URL 参数进行重新排序。尽管现代浏览器通常会保留参数的原始顺序,但不排除某些情况下会有特殊的处理。
  4. 请求过程 : 也许是 get 请求方法导致。

二、解决方法

针对这个问题,我们可以从多个方面入手,逐步优化,最终彻底解决它。

1. 统一参数顺序 (前端)

在前端构建 URL 时,手动保证参数顺序的一致性。这是最直接、最可控的方法。

原理: 通过 JavaScript 对象来控制参数的顺序。创建一个对象,按你希望的顺序添加属性,然后使用 URLSearchParams 将其转换为查询字符串。

操作步骤:

修改 list.vue 中构建 URL 的部分:

// 假设你希望的顺序是 page, sort, direction

const params = {
    page: 1,
    sort: sort.value,
    direction: direction.value,
};

const queryString = new URLSearchParams(params).toString();

router.get(route('admin.users.list') + '?' + queryString);

进阶使用:

  • 将参数处理封装到一个公共函数里面。
// utils/url.js

export function buildUrlWithParams(routeName, params) {
    const orderedParams = {};
     // 你想要的顺序
    const desiredOrder = ['page', 'sort', 'direction'];

    desiredOrder.forEach(key => {
        if (params.hasOwnProperty(key)) {
            orderedParams[key] = params[key];
        }
    });
     // 将剩余的其他参数添加到后面
   for (const key in params) {
        if (!desiredOrder.includes(key)) {
            orderedParams[key] = params[key];
        }
    }
    const queryString = new URLSearchParams(orderedParams).toString();
    return route(routeName) + '?' + queryString;
}

list.vue 中使用:

import { buildUrlWithParams } from '@/utils/url';

// ...

router.get(buildUrlWithParams('admin.users.list', {
    direction: direction.value,
    page: 1,
    sort: sort.value,
}));

2. 使用 preserveState (Inertia.js)

如果你的前端使用的是 Inertia.js,可以考虑使用 preserveState 选项。

原理: preserveState 告诉 Inertia.js 在页面转换时保留当前页面的状态(包括 URL 参数)。

操作步骤:

router.get(route('admin.users.list'), {
        direction: direction.value,
        page: 1,
        sort: sort.value,
    }, { preserveState: true });

注意: preserveState 可能不总是最佳选择,因为它可能会导致一些意料之外的状态保持问题,尤其是当你的应用中有复杂的交互时,所以只在必要的时候使用它.

3. 修改 Pagination 组件(pagination.vue

问题的图片里面展示的是分页有问题, pagination.vue里面需要保持参数一致性.

<template>
  <div>
      <a v-if="pagination.prev_page_url" :href="buildCustomUrl(pagination.prev_page_url)">Previous</a>
      <!--  其他分页链接  -->
      <a v-if="pagination.next_page_url" :href="buildCustomUrl(pagination.next_page_url)">Next</a>
  </div>
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({
   pagination: Object,
});

const buildCustomUrl = (url) => {

    if(!url) {
        return '#'; // 或者返回其他你觉得合适的默认值
    }

    const parsedUrl = new URL(url);
    const params = new URLSearchParams(parsedUrl.search);

      const orderedParams = {};
      const desiredOrder = ['page', 'sort', 'direction']; // 定义你希望的顺序

      desiredOrder.forEach(key => {
        if (params.has(key)) {
            orderedParams[key] = params.get(key);
            params.delete(key);
        }
    });
     //加入剩余的参数
    params.forEach((value, key) => {
        orderedParams[key] = value;
    });

    parsedUrl.search = new URLSearchParams(orderedParams).toString();

    return parsedUrl.toString();

};
</script>

4. 自定义分页链接 (Laravel) -- 如果以上前端控制还不行,可以从后端入手。

如果前端方案都无法解决问题,或者你希望从后端更彻底地控制分页链接,可以考虑自定义 Laravel 的分页链接生成逻辑。

原理: 重写 Laravel 的分页视图,并在其中自定义 URL 生成逻辑。

操作步骤:

  1. 创建自定义分页视图:

    resources/views/vendor/pagination 目录下创建一个新的 Blade 视图文件,例如 custom.blade.php

    @if ($paginator->hasPages())
        <nav>
            <ul class="pagination">
                {{-- Previous Page Link --}}
                @if ($paginator->onFirstPage())
                    <li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
                        <span class="page-link" aria-hidden="true">&lsaquo;</span>
                    </li>
                @else
                    <li class="page-item">
                        <a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">&lsaquo;</a>
                    </li>
                @endif
    
                {{-- Pagination Elements --}}
                @foreach ($elements as $element)
                    {{-- "Three Dots" Separator --}}
                    @if (is_string($element))
                        <li class="page-item disabled" aria-disabled="true"><span class="page-link">{{ $element }}</span></li>
                    @endif
    
                    {{-- Array Of Links --}}
                    @if (is_array($element))
                        @foreach ($element as $page => $url)
                            @if ($page == $paginator->currentPage())
                                <li class="page-item active" aria-current="page"><span class="page-link">{{ $page }}</span></li>
                            @else
                                <li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
                            @endif
                        @endforeach
                    @endif
                @endforeach
    
                {{-- Next Page Link --}}
                @if ($paginator->hasMorePages())
                    <li class="page-item">
                        <a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">&rsaquo;</a>
                    </li>
                @else
                    <li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
                        <span class="page-link" aria-hidden="true">&rsaquo;</span>
                    </li>
                @endif
            </ul>
        </nav>
    @endif
    

将此文件里面的  `href="{{ $paginator->previousPageUrl() }}"`, 和`href="{{ $paginator->nextPageUrl() }}"`的 `$url` 参数值都传入处理参数排序的自定义函数。

2.  **自定义 URL 生成函数:** 
创建一个辅助函数或在相关的服务类里面定义一个方法处理url,参考前端 `buildCustomUrl`函数的逻辑。
把`$url`传进去即可.
3.  **修改 `AppServiceProvider`:** 
```php
use Illuminate\Pagination\Paginator;

public function boot()
{
 Paginator::defaultView('vendor.pagination.custom');

 // 或者,如果只想针对简单分页使用自定义视图:
 // Paginator::defaultSimpleView('vendor.pagination.custom-simple');
}

5. 尝试 POST 请求 (终极方案,非必要不使用)

上面几个都解决不了的时候,最终方案,我们可以使用 POST 替换GET

router.post(route('admin.users.list'), {
        direction: direction.value,
        page: 1,
        sort: sort.value,
    });

服务端:

// web.php
Route::post('/admin/users/list', [UserController::class, 'list']);

因为 POST 请求的参数通常在请求体中,不在 URL 中,这样也就彻底避免了 URL 参数闪烁的问题.但这种做法改变了请求的语义 。从 RESTful API 设计的角度来看,GET 请求更适合用于获取数据,而 POST 请求通常用于创建或修改资源, 因此需要慎重选择.

三、小结

地址栏参数闪烁虽然是个小问题,要完全避免还真是不容易,我们可以通过在前端或后端采取措施来解决。通常情况下,优先选择前端方案,因为它更简单、更直接, 且具有更高的灵活性。实在解决不了就自定义后端的分页,最后如果还不行,就改post. 通常第一步统一前端顺序就能解决大多数情况下的问题.

祝你顺利解决这个问题!