adesin-fr / inertiajs-tables-laravel-query-builder
Inertia.js 前端组件,用于 Spatie 的 Laravel 查询构建器
Requires
- php: ^8.2
- illuminate/support: ^11.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.8
- inertiajs/inertia-laravel: ^1.0
- orchestra/testbench: ^9.0
- phpunit/phpunit: ^11.0
README
分支原因
本包为 Inertia.js 提供类似 DataTables 的体验,支持搜索、过滤、排序、切换列和分页。它生成的 URL 可以由 Spatie 的优秀 Laravel 查询构建器 包使用,无需额外的逻辑。组件使用 Tailwind CSS 3.0 进行样式化,但可以通过插槽进行完全自定义。数据刷新逻辑基于 Inertia 的 Ping CRM 示例。
由于某些质量问题,我不满意 [PonchRobles/inertiajs-tables-laravel-query-builder],因此将其分支出来。
功能
- 自动填充:自动生成带有自定义单元格支持的
thead
和tbody
- 全局搜索
- 按字段搜索
- 选择过滤器
- 切换列
- 排序列
- 分页(支持 Eloquent/API 资源/Simple/Cursor)
- 自动更新查询字符串(使用 Inertia 的替换功能)
兼容性
- Vue 3
- Laravel 11
- Inertia.js
- Tailwind CSS v3 + 表单插件
- PHP 8.2+
安装
您需要安装服务器端包和客户端包。请注意,此包仅与 Laravel 10、Vue 3.0 兼容,并需要 Tailwind Forms 插件。
服务器端安装(Laravel)
您可以通过 Composer 安装此包
composer require AdesinFr/inertiajs-tables-laravel-query-builder
此包将自动注册服务提供者,提供可在 Interia 响应上使用的 table
方法。
搜索字段
使用 searchInput
方法,您可以指定哪些属性是可搜索的。搜索查询作为 filter
传递到 URL 查询中。这与 Laravel 查询构建器包的 过滤功能 无缝集成。
尽管传递列键就足够了,但您也可以指定自定义标签和默认值。
use AdesinFr\LaravelQueryBuilderInertiaJs\InertiaTable; Inertia::render('Page/Index')->table(function (InertiaTable $table) { $table->searchInput('name'); $table->searchInput( key: 'framework', label: 'Find your framework', defaultValue: 'Laravel' ); });
选择过滤器
选择过滤器与搜索字段类似,但使用 select
元素而不是 input
元素。这样,您可以向用户提供一组预定义的选项。底层使用的是 Laravel 查询构建器包的相同过滤功能。
selectFilter
方法需要两个参数:键和选项的键值数组。
Inertia::render('Page/Index')->table(function (InertiaTable $table) { $table->selectFilter('language_code', [ 'en' => 'Engels', 'nl' => 'Nederlands', ]); });
selectFilter
将默认添加一个 无过滤器 选项到数组中。您可以禁用此选项或为其指定一个自定义标签。
Inertia::render('Page/Index')->table(function (InertiaTable $table) { $table->selectFilter( key: 'language_code', options: $languages, label: 'Language', defaultValue: 'nl', noFilterOption: true, noFilterOptionLabel: 'All languages' ); });
布尔过滤器
这样,您可以使用切换按钮向用户提供选择。底层使用的是 Laravel 查询构建器包的相同过滤功能。
toggleFilter
方法需要一个参数:键。
Inertia::render('Page/Index')->table(function (InertiaTable $table) { $table->toggleFilter('is_verified'); });
您可以为其指定一个自定义标签和一个默认值。
Inertia::render('Page/Index')->table(function (InertiaTable $table) { $table->toggleFilter( key: 'is_verified', label: 'Is email verified', defaultValue: true, ); });
数字范围过滤器
这样,您可以使用切换按钮向用户提供选择。底层使用的是 Laravel 查询构建器包的相同过滤功能。
numberRangeFilter
方法需要两个参数:键和最大值。
Inertia::render('Page/Index')->table(function (InertiaTable $table) { $table->numberRangeFilter('invoice_recall_count', 5); });
您可以指定一些其他参数。
Inertia::render('Page/Index')->table(function (InertiaTable $table) { $table->toggleFilter( key: 'invoice_recall_count', max: 5, min: 0, prefix: '', suffix: '', step: 1, label: 'Invoice recall count', defaultValue: [1,4], ); });
您需要为此过滤器使用自定义允许的过滤器。
$users = QueryBuilder::for(/*...*/) ->allowedFilters([NumberRangeFilter::getQueryBuilderFilter('invoice_recall_count')]);
列
使用column
方法,您可以指定哪些列可以切换、排序和搜索。您必须为每个列传递至少一个键或标签。
Inertia::render('Page/Index')->table(function (InertiaTable $table) { $table->column('name', 'User Name'); $table->column( key: 'name', label: 'User Name', canBeHidden: true, hidden: false, sortable: true, searchable: true ); });
searchable
选项是searchInput
方法的快捷方式。下面的示例将本质上调用$table->searchInput('name', '用户名')
。
全局搜索
您可以使用withGlobalSearch
方法启用全局搜索,并可选地指定一个占位符。
Inertia::render('Page/Index')->table(function (InertiaTable $table) { $table->withGlobalSearch(); $table->withGlobalSearch('Search through the data...'); });
如果您想默认启用每个表的全局搜索,您可以使用静态defaultGlobalSearch
方法,例如在AppServiceProvider
类中
InertiaTable::defaultGlobalSearch(); InertiaTable::defaultGlobalSearch('Default custom placeholder'); InertiaTable::defaultGlobalSearch(false); // disable
示例控制器
<?php namespace App\Http\Controllers; use App\Models\User; use Illuminate\Support\Collection; use Inertia\Inertia; use AdesinFr\LaravelQueryBuilderInertiaJs\InertiaTable; use Spatie\QueryBuilder\AllowedFilter; use Spatie\QueryBuilder\QueryBuilder; class UserIndexController { public function __invoke() { $globalSearch = AllowedFilter::callback('global', function ($query, $value) { $query->where(function ($query) use ($value) { Collection::wrap($value)->each(function ($value) use ($query) { $query ->orWhere('name', 'LIKE', "%{$value}%") ->orWhere('email', 'LIKE', "%{$value}%"); }); }); }); $users = QueryBuilder::for(User::class) ->defaultSort('name') ->allowedSorts(['name', 'email', 'language_code']) ->allowedFilters(['name', 'email', 'language_code', $globalSearch]) ->paginate() ->withQueryString(); return Inertia::render('Users/Index', [ 'users' => $users, ])->table(function (InertiaTable $table) { $table ->withGlobalSearch() ->defaultSort('name') ->column(key: 'name', searchable: true, sortable: true, canBeHidden: false) ->column(key: 'email', searchable: true, sortable: true) ->column(key: 'language_code', label: 'Language') ->column(label: 'Actions') ->selectFilter(key: 'language_code', label: 'Language', options: [ 'en' => 'English', 'nl' => 'Dutch', ]); } }
客户端安装(Inertia)
您可以通过npm
或yarn
安装此软件包。
npm install AdesinFr/inertiajs-tables-laravel-query-builder --save yarn add AdesinFr/inertiajs-tables-laravel-query-builder
将存储库路径添加到您的content
数组中的Tailwind配置文件。这确保了样式在生产构建中也有效。
module.exports = { content: [ './node_modules/AdesinFr/inertiajs-tables-laravel-query-builder/**/*.{js,vue}', ] }
表格组件
要使用Table
组件及其所有相关功能,您必须导入Table
组件并将users
数据传递给组件。
<script setup> import { Table } from "AdesinFr/inertiajs-tables-laravel-query-builder"; defineProps(["users"]) </script> <template> <Table :resource="users" /> </template>
resource
属性会自动检测数据和额外的分页元数据。您也可以通过data
和meta
属性手动将此传递给组件。
<template> <Table :data="users.data" :meta="users.meta" /> </template>
如果您想手动渲染表格,就像这个软件包的v1版本那样,您可以使用head
和body
插槽。此外,您仍然可以使用meta
属性来渲染分页器。
<template> <Table :meta="users"> <template #head> <tr> <th>User</th> </tr> </template> <template #body> <tr v-for="(user, key) in users.data" :key="key" > <td>{{ user.name }}</td> </tr> </template> </Table> </template>
Table
有一些额外的属性可以调整其前端行为。
<template> <Table :striped="true" :prevent-overlapping-requests="false" :input-debounce-ms="1000" :preserve-scroll="true" /> </template>
Table
有一些您可以使用的事件
- rowClicked:当用户点击行时,会触发此事件。此事件提供以下信息:事件、项、键。如果您在行内部使用可点击元素(如操作按钮)时使用此事件,请务必使用
event.stopPropagation()
。
自定义列单元格
当使用自动填充时,您可能想要在保持其他列不受影响的情况下转换特定列显示的数据。为此,您可以使用单元格模板。此示例取自上面的示例控制器。
<template> <Table :resource="users"> <template #cell(actions)="{ item: user }"> <a :href="`/users/${user.id}/edit`"> Edit </a> </template> </Table> </template>
自定义表头单元格
当使用自动填充时,您可能想要在保持其他列不受影响的情况下转换特定表头显示的数据。为此,您可以使用表头模板。此示例取自上面的示例控制器。
<template> <Table :resource="users"> <template #header(email)="{ label: label, column: column }"> <span class="lowercase">{{ label }}</span> </template> </Table> </template>
每页多个表格
您可能希望在一页中使用多个表格组件。显示数据很简单,但使用像过滤、排序和分页这样的功能需要稍有不同的设置。例如,默认情况下,使用page
查询键对数据集进行分页,但现在您想为每个表格使用不同的键。幸运的是,这个软件包会处理这个问题,甚至提供了一个辅助方法来支持Spatie的查询包。为了使其正常工作,您需要为表格命名。
让我们看看Spatie的QueryBuilder
。在这个例子中,有一个用于公司的表格和一个用于用户的表格。我们相应地命名了这些表格。首先,调用静态updateQueryBuilderParameters
方法,告诉软件包使用不同的查询参数集。现在,filter
变为companies_filter
,column
变为companies_column
,依此类推。其次,更改数据库分页器的pageName
。
InertiaTable::updateQueryBuilderParameters('companies'); $companies = QueryBuilder::for(Company::query()) ->defaultSort('name') ->allowedSorts(['name', 'email']) ->allowedFilters(['name', 'email']) ->paginate(pageName: 'companiesPage') ->withQueryString(); InertiaTable::updateQueryBuilderParameters('users'); $users = QueryBuilder::for(User::query()) ->defaultSort('name') ->allowedSorts(['name', 'email']) ->allowedFilters(['name', 'email']) ->paginate(pageName: 'usersPage') ->withQueryString();
然后,我们需要将这些更改应用到InertiaTable
类中。有一个name
和pageName
方法可以做到这一点。
return Inertia::render('TwoTables', [ 'companies' => $companies, 'users' => $users, ])->table(function (InertiaTable $inertiaTable) { $inertiaTable ->name('users') ->pageName('usersPage') ->defaultSort('name') ->column(key: 'name', searchable: true) ->column(key: 'email', searchable: true); })->table(function (InertiaTable $inertiaTable) { $inertiaTable ->name('companies') ->pageName('companiesPage') ->defaultSort('name') ->column(key: 'name', searchable: true) ->column(key: 'address', searchable: true); });
最后,在Vue模板中的每个表格中传递正确的name
属性。可选地,您可以设置preserve-scroll
属性为table-top
。这确保在新的数据上滚动到表格顶部。例如,在更改第二个表格的页面时,您希望滚动到表格顶部,而不是页面顶部。
<script setup> import { Table } from "AdesinFr/inertiajs-tables-laravel-query-builder"; defineProps(["companies", "users"]) </script> <template> <Table :resource="companies" name="companies" preserve-scroll="table-top" /> <Table :resource="users" name="users" preserve-scroll="table-top" /> </template>
分页翻译
您可以使用setTranslations
方法覆盖默认的分页翻译。您可以在主JavaScript文件中这样做。
import { setTranslations } from "AdesinFr/inertiajs-tables-laravel-query-builder"; setTranslations({ next: "Next", no_results_found: "No results found", of: "of", per_page: "per page", previous: "Previous", results: "results", to: "to", search: "Search", reset: "Reset", grouped_reset: "Reset", add_search_fields: "Add search field", show_hide_columns: "Show / Hide columns", });
Table.vue插槽
Table.vue
有几个插槽,您可以使用它们来注入自己的实现。
每个插槽都提供了props来与父Table
组件交互。
<template> <Table> <template v-slot:tableGlobalSearch="slotProps"> <input placeholder="Custom Global Search Component..." @input="slotProps.onChange($event.target.value)" /> </template> </Table> </template>
可用的自定义
您可以自定义表格的一些部分。
在app.js
文件中提供包含所需自定义的对象,如下所示
const themeVariables = { inertia_table: { per_page_selector: { select: { primary: 'your classes', }, }, }, } createInertiaApp({ progress: { color: '#4B5563', }, title: (title) => `${title} - ${appName}`, resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')), setup({ el, App, props, plugin }) { return createApp({ render: () => h(App, props) }) // ... .provide('themeVariables', themeVariables) // ... .mount(el); }, })
您可以通过覆盖默认样式来自定义默认样式
const themeVariables = { inertia_table: { per_page_selector: { select: { base: "block min-w-max shadow-sm text-sm rounded-md", color: { primary: "border-gray-300 focus:ring-yellow-500 focus:border-yellow-500", }, }, }, }, }
或者您可以创建一个新的样式,并在Table.vue
上使用color
prop
const themeVariables = { inertia_table: { select: { base: "block min-w-max shadow-sm text-sm rounded-md", color: { red_style: 'border-gray-300 focus:ring-red-500 focus:border-red-500', }, }, }, }
<template> <Table color="red_style" /> </template>
可用的自定义
const themeVariables = { inertia_table: { button_with_dropdown: { button: { base: "w-full border rounded-md shadow-sm px-4 py-2 inline-flex justify-center text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2", color: { primary: "bg-white text-gray-700 hover:bg-gray-50 border-gray-300 focus:ring-indigo-500", dootix: "bg-white text-gray-700 hover:bg-gray-50 border-gray-300 focus:ring-cyan-500", }, }, }, per_page_selector: { select: { base: "block min-w-max shadow-sm text-sm rounded-md", color: { primary: "border-gray-300 focus:ring-indigo-500 focus:border-indigo-500", dootix: "border-gray-300 focus:ring-cyan-500 focus:border-blue-500", }, }, }, table_filter: { select_filter: { select: { base: "block w-full shadow-sm text-sm rounded-md", color: { primary: "border-gray-300 focus:ring-indigo-500 focus:border-indigo-500", dootix: "border-gray-300 focus:ring-cyan-500 focus:border-blue-500", }, }, }, togle_filter: { toggle: { base: "w-11 h-6 rounded-full after:border after:rounded-full after:h-5 after:w-5", color: { primary: "after:bg-white after:border-white peer-checked:bg-indigo-500 bg-red-500", dootix: "after:bg-white after:border-white peer-checked:bg-gradient-to-r peer-checked:from-cyan-500 peer-checked:to-blue-600 bg-red-500", disabled: "after:bg-white after:border-white bg-gray-200", } }, reset_button: { base: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2", color: { primary: "text-gray-400 hover:text-gray-500 focus:ring-indigo-500", dootix: "text-gray-400 hover:text-gray-500 focus:ring-cyan-500", }, }, number_range_filter: { main_bar: { base: "h-2 rounded-full", color: { primary: "bg-gray-200", dootix: "bg-gray-200", }, }, selected_bar: { base: "h-2 rounded-full", color: { primary: "bg-indigo-600", dootix: "bg-gradient-to-r from-cyan-500 to-blue-600", }, }, button: { base: "h-4 w-4 rounded-full shadow border", color: { primary: "bg-white border-gray-300", dootix: "bg-white border-gray-300", }, }, popover: { base: "truncate text-xs rounded py-1 px-4", color: { primary: "bg-gray-600 text-white", dootix: "bg-gray-600 text-white", }, }, popover_arrow: { color: { primary: "text-gray-600", dootix: "text-gray-600", }, }, text: { color: { primary: "text-gray-700", dootix: "text-gray-700", }, }, global_search: { base: "block w-full pl-9 text-sm rounded-md shadow-sm", color: { primary: "focus:ring-indigo-500 focus:border-indigo-500 border-gray-300", dootix: "focus:ring-cyan-500 focus:border-blue-500 border-gray-300", }, }, reset_button: { base: "w-full border rounded-md shadow-sm px-4 py-2 inline-flex justify-center text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2", color: { primary: "bg-white text-gray-700 hover:bg-gray-50 border-gray-300 focus:ring-indigo-500", dootix: "bg-white text-gray-700 hover:bg-gray-50 border-gray-300 focus:ring-cyan-500", }, }, table_search_rows: { input: { base: "flex-1 min-w-0 block w-full px-3 py-2 rounded-none rounded-r-md text-sm", color: { primary: "border-gray-300 focus:ring-indigo-500 focus:border-indigo-500", dootix: "border-gray-300 focus:ring-cyan-500 focus:border-blue-500", }, }, remove_button: { base: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2", color: { primary: "text-gray-400 hover:text-gray-500 focus:ring-indigo-500", dootix: "text-gray-400 hover:text-gray-500 focus:ring-cyan-500", }, }, }, }, }
测试
可以在app
目录中找到一个巨大的Laravel Dusk端到端测试套件。在这里您将找到一个Laravel + Inertia应用程序。
cd app
cp .env.example .env
composer install
npm install
npm run production
touch database/database.sqlite
php artisan migrate:fresh --seed
php artisan dusk:chrome-driver
php artisan serve
php artisan dusk
从v1升级
服务器端
addColumn
方法已重命名为column
。addFilter
方法已重命名为selectFilter
。addSearch
方法已重命名为searchInput
。- 对于所有重命名的方法,请查看参数,因为其中一些已更改。
- 已删除
addColumns
和addSearchRows
方法。 - 全局搜索不再默认启用。
客户端
InteractsWithQueryBuilder
混合已被删除,不再需要。Table
组件不再需要filters
、search
、columns
和on-update
属性。- 当使用自定义
thead
或tbody
插槽时,您需要手动提供样式。 - 当使用自定义
thead
时,showColumn
方法已重命名为show
。 setTranslations
方法不再是Pagination
组件的一部分,但应该导入。- 组件的模板和逻辑不再分离。使用插槽来注入自己的实现。
变更日志
有关最近更改的更多信息,请参阅变更日志。
贡献
有关详细信息,请参阅贡献指南。
安全
如果您发现任何安全相关的问题,请通过电子邮件alfonsorodriguez@live.com.mx联系,而不是使用问题跟踪器。
鸣谢
许可证
MIT许可证(MIT)。有关更多信息,请参阅许可证文件。