laravel-inertia-vue
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLaravel + Inertia.js + Vue 3
Laravel + Inertia.js + Vue 3
Comprehensive patterns for building modern monolithic applications with Laravel, Inertia.js, and Vue 3. Contains 30+ rules for seamless full-stack development.
这是一套使用Laravel、Inertia.js和Vue 3构建现代单体应用的综合模式,包含30+条实现无缝全栈开发的规则。
When to Apply
适用场景
Reference these guidelines when:
- Creating Inertia page components with Vue 3
- Handling forms with useForm composable
- Managing shared data and authentication
- Implementing persistent layouts
- Navigating between pages
在以下场景中可参考本指南:
- 创建基于Vue 3的Inertia页面组件
- 使用useForm组合式函数处理表单
- 管理共享数据与认证信息
- 实现持久化布局
- 页面间导航
Rule Categories by Priority
按优先级划分的规则类别
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Page Components | CRITICAL | |
| 2 | Forms & Validation | CRITICAL | |
| 3 | Navigation & Links | HIGH | |
| 4 | Shared Data | HIGH | |
| 5 | Layouts | MEDIUM | |
| 6 | File Uploads | MEDIUM | |
| 7 | Advanced Patterns | LOW | |
| 优先级 | 类别 | 影响程度 | 前缀 |
|---|---|---|---|
| 1 | 页面组件 | 关键 | |
| 2 | 表单与验证 | 关键 | |
| 3 | 导航与链接 | 高 | |
| 4 | 共享数据 | 高 | |
| 5 | 布局 | 中 | |
| 6 | 文件上传 | 中 | |
| 7 | 高级模式 | 低 | |
Quick Reference
快速参考
1. Page Components (CRITICAL)
1. 页面组件(关键)
- - Type page props from Laravel
page-props-typing - - Standard page component pattern
page-component-structure - - Title and meta tags with Head
page-head-management - - Assign layouts to pages
page-default-layout
- - 为来自Laravel的页面props添加类型
page-props-typing - - 标准化页面组件模式
page-component-structure - - 使用Head管理标题与元标签
page-head-management - - 为页面分配布局
page-default-layout
2. Forms & Validation (CRITICAL)
2. 表单与验证(关键)
- - Basic useForm usage
form-useform-basic - - Display Laravel validation errors
form-validation-errors - - Handle form submission state
form-processing-state - - Reset vs preserve form data
form-reset-preserve - - Transform data before submit
form-transform
- - useForm基础用法
form-useform-basic - - 展示Laravel验证错误信息
form-validation-errors - - 处理表单提交状态
form-processing-state - - 重置与保留表单数据的处理
form-reset-preserve - - 提交前转换数据
form-transform
3. Navigation & Links (HIGH)
3. 导航与链接(高)
- - Use Link for navigation
nav-link-component - - Preserve scroll and state
nav-preserve-state - - Reload only what changed
nav-partial-reloads - - Replace vs push history
nav-replace-history
- - 使用Link组件进行导航
nav-link-component - - 保留滚动位置与页面状态
nav-preserve-state - - 仅重新加载变更内容
nav-partial-reloads - - 替换与推送历史记录的区别
nav-replace-history
4. Shared Data (HIGH)
4. 共享数据(高)
- - Access authenticated user
shared-auth-user - - Handle flash messages
shared-flash-messages - - Access global props
shared-global-props - - Type shared data
shared-typescript
- - 访问已认证用户信息
shared-auth-user - - 处理闪存消息
shared-flash-messages - - 访问全局props
shared-global-props - - 为共享数据添加类型
shared-typescript
5. Layouts (MEDIUM)
5. 布局(中)
- - Persistent layouts pattern
layout-persistent - - Nested layouts
layout-nested - - Default layout assignment
layout-default - - Conditional layouts
layout-conditional
- - 持久化布局模式
layout-persistent - - 嵌套布局
layout-nested - - 默认布局分配
layout-default - - 条件式布局
layout-conditional
6. File Uploads (MEDIUM)
6. 文件上传(中)
- - Basic file upload
upload-basic - - Upload progress tracking
upload-progress - - Multiple file uploads
upload-multiple
- - 基础文件上传
upload-basic - - 上传进度追踪
upload-progress - - 多文件上传
upload-multiple
7. Advanced Patterns (LOW)
7. 高级模式(低)
- - Real-time polling
advanced-polling - - Prefetch pages
advanced-prefetch - - Modal as pages
advanced-modal-pages - - Infinite scrolling
advanced-infinite-scroll
- - 实时轮询
advanced-polling - - 页面预加载
advanced-prefetch - - 将模态框作为页面处理
advanced-modal-pages - - 无限滚动
advanced-infinite-scroll
Essential Patterns
核心模式
Page Component with TypeScript
带TypeScript的页面组件
vue
<!-- resources/js/Pages/Posts/Index.vue -->
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3'
interface Post {
id: number
title: string
excerpt: string
created_at: string
author: {
id: number
name: string
}
}
interface Props {
posts: {
data: Post[]
links: { url: string | null; label: string; active: boolean }[]
}
filters: {
search?: string
}
}
const props = defineProps<Props>()
</script>
<template>
<Head title="Posts" />
<div class="container mx-auto py-8">
<h1 class="text-2xl font-bold mb-6">Posts</h1>
<div class="space-y-4">
<article
v-for="post in props.posts.data"
:key="post.id"
class="p-4 bg-white rounded-lg shadow"
>
<Link :href="route('posts.show', post.id)">
<h2 class="text-xl font-semibold hover:text-blue-600">
{{ post.title }}
</h2>
</Link>
<p class="text-gray-600 mt-2">{{ post.excerpt }}</p>
<p class="text-sm text-gray-400 mt-2">By {{ post.author.name }}</p>
</article>
</div>
</div>
</template>vue
<!-- resources/js/Pages/Posts/Index.vue -->
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3'
interface Post {
id: number
title: string
excerpt: string
created_at: string
author: {
id: number
name: string
}
}
interface Props {
posts: {
data: Post[]
links: { url: string | null; label: string; active: boolean }[]
}
filters: {
search?: string
}
}
const props = defineProps<Props>()
</script>
<template>
<Head title="Posts" />
<div class="container mx-auto py-8">
<h1 class="text-2xl font-bold mb-6">Posts</h1>
<div class="space-y-4">
<article
v-for="post in props.posts.data"
:key="post.id"
class="p-4 bg-white rounded-lg shadow"
>
<Link :href="route('posts.show', post.id)">
<h2 class="text-xl font-semibold hover:text-blue-600">
{{ post.title }}
</h2>
</Link>
<p class="text-gray-600 mt-2">{{ post.excerpt }}</p>
<p class="text-sm text-gray-400 mt-2">By {{ post.author.name }}</p>
</article>
</div>
</div>
</template>Form with useForm
使用useForm的表单
vue
<!-- resources/js/Pages/Posts/Create.vue -->
<script setup lang="ts">
import { Head, Link, useForm } from '@inertiajs/vue3'
interface Category {
id: number
name: string
}
interface Props {
categories: Category[]
}
const props = defineProps<Props>()
const form = useForm({
title: '',
body: '',
category_id: '',
})
function submit() {
form.post(route('posts.store'), {
onSuccess: () => form.reset(),
})
}
</script>
<template>
<Head title="Create Post" />
<form @submit.prevent="submit" class="max-w-2xl mx-auto py-8">
<div class="mb-4">
<label for="title" class="block font-medium mb-1">Title</label>
<input
id="title"
v-model="form.title"
type="text"
class="w-full border rounded px-3 py-2"
/>
<p v-if="form.errors.title" class="text-red-500 text-sm mt-1">
{{ form.errors.title }}
</p>
</div>
<div class="mb-4">
<label for="category" class="block font-medium mb-1">Category</label>
<select
id="category"
v-model="form.category_id"
class="w-full border rounded px-3 py-2"
>
<option value="">Select a category</option>
<option v-for="cat in props.categories" :key="cat.id" :value="cat.id">
{{ cat.name }}
</option>
</select>
<p v-if="form.errors.category_id" class="text-red-500 text-sm mt-1">
{{ form.errors.category_id }}
</p>
</div>
<div class="mb-4">
<label for="body" class="block font-medium mb-1">Content</label>
<textarea
id="body"
v-model="form.body"
rows="10"
class="w-full border rounded px-3 py-2"
/>
<p v-if="form.errors.body" class="text-red-500 text-sm mt-1">
{{ form.errors.body }}
</p>
</div>
<div class="flex gap-4">
<button
type="submit"
:disabled="form.processing"
class="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
{{ form.processing ? 'Creating...' : 'Create Post' }}
</button>
<Link :href="route('posts.index')" class="px-4 py-2 border rounded">
Cancel
</Link>
</div>
</form>
</template>vue
<!-- resources/js/Pages/Posts/Create.vue -->
<script setup lang="ts">
import { Head, Link, useForm } from '@inertiajs/vue3'
interface Category {
id: number
name: string
}
interface Props {
categories: Category[]
}
const props = defineProps<Props>()
const form = useForm({
title: '',
body: '',
category_id: '',
})
function submit() {
form.post(route('posts.store'), {
onSuccess: () => form.reset(),
})
}
</script>
<template>
<Head title="Create Post" />
<form @submit.prevent="submit" class="max-w-2xl mx-auto py-8">
<div class="mb-4">
<label for="title" class="block font-medium mb-1">Title</label>
<input
id="title"
v-model="form.title"
type="text"
class="w-full border rounded px-3 py-2"
/>
<p v-if="form.errors.title" class="text-red-500 text-sm mt-1">
{{ form.errors.title }}
</p>
</div>
<div class="mb-4">
<label for="category" class="block font-medium mb-1">Category</label>
<select
id="category"
v-model="form.category_id"
class="w-full border rounded px-3 py-2"
>
<option value="">Select a category</option>
<option v-for="cat in props.categories" :key="cat.id" :value="cat.id">
{{ cat.name }}
</option>
</select>
<p v-if="form.errors.category_id" class="text-red-500 text-sm mt-1">
{{ form.errors.category_id }}
</p>
</div>
<div class="mb-4">
<label for="body" class="block font-medium mb-1">Content</label>
<textarea
id="body"
v-model="form.body"
rows="10"
class="w-full border rounded px-3 py-2"
/>
<p v-if="form.errors.body" class="text-red-500 text-sm mt-1">
{{ form.errors.body }}
</p>
</div>
<div class="flex gap-4">
<button
type="submit"
:disabled="form.processing"
class="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
{{ form.processing ? 'Creating...' : 'Create Post' }}
</button>
<Link :href="route('posts.index')" class="px-4 py-2 border rounded">
Cancel
</Link>
</div>
</form>
</template>Persistent Layout
持久化布局
vue
<!-- resources/js/Layouts/AppLayout.vue -->
<script setup lang="ts">
import { Link, usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
const page = usePage()
const auth = computed(() => page.props.auth as { user: { name: string } })
</script>
<template>
<div class="min-h-screen bg-gray-100">
<nav class="bg-white shadow">
<div class="container mx-auto px-4 py-3 flex justify-between">
<Link href="/" class="font-bold">My App</Link>
<span>Welcome, {{ auth.user.name }}</span>
</div>
</nav>
<main class="container mx-auto px-4 py-8">
<slot />
</main>
</div>
</template>vue
<!-- resources/js/Pages/Dashboard.vue -->
<script setup lang="ts">
import AppLayout from '@/Layouts/AppLayout.vue'
defineOptions({ layout: AppLayout })
</script>
<template>
<h1>Dashboard</h1>
</template>vue
<!-- resources/js/Layouts/AppLayout.vue -->
<script setup lang="ts">
import { Link, usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
const page = usePage()
const auth = computed(() => page.props.auth as { user: { name: string } })
</script>
<template>
<div class="min-h-screen bg-gray-100">
<nav class="bg-white shadow">
<div class="container mx-auto px-4 py-3 flex justify-between">
<Link href="/" class="font-bold">My App</Link>
<span>Welcome, {{ auth.user.name }}</span>
</div>
</nav>
<main class="container mx-auto px-4 py-8">
<slot />
</main>
</div>
</template>vue
<!-- resources/js/Pages/Dashboard.vue -->
<script setup lang="ts">
import AppLayout from '@/Layouts/AppLayout.vue'
defineOptions({ layout: AppLayout })
</script>
<template>
<h1>Dashboard</h1>
</template>Laravel Controller
Laravel控制器
php
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;
class PostController extends Controller
{
public function index(): Response
{
return Inertia::render('Posts/Index', [
'posts' => Post::with('author:id,name')
->latest()
->paginate(10),
'filters' => request()->only('search'),
]);
}
public function create(): Response
{
return Inertia::render('Posts/Create', [
'categories' => Category::all(['id', 'name']),
]);
}
public function store(StorePostRequest $request): RedirectResponse
{
$post = Post::create([
...$request->validated(),
'user_id' => auth()->id(),
]);
return redirect()
->route('posts.show', $post)
->with('success', 'Post created successfully.');
}
public function show(Post $post): Response
{
return Inertia::render('Posts/Show', [
'post' => $post->load('author', 'category'),
]);
}
}php
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;
class PostController extends Controller
{
public function index(): Response
{
return Inertia::render('Posts/Index', [
'posts' => Post::with('author:id,name')
->latest()
->paginate(10),
'filters' => request()->only('search'),
]);
}
public function create(): Response
{
return Inertia::render('Posts/Create', [
'categories' => Category::all(['id', 'name']),
]);
}
public function store(StorePostRequest $request): RedirectResponse
{
$post = Post::create([
...$request->validated(),
'user_id' => auth()->id(),
]);
return redirect()
->route('posts.show', $post)
->with('success', 'Post created successfully.');
}
public function show(Post $post): Response
{
return Inertia::render('Posts/Show', [
'post' => $post->load('author', 'category'),
]);
}
}Shared Data (HandleInertiaRequests)
共享数据(HandleInertiaRequests)
php
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'auth' => [
'user' => $request->user() ? [
'id' => $request->user()->id,
'name' => $request->user()->name,
'email' => $request->user()->email,
] : null,
],
'flash' => [
'success' => $request->session()->get('success'),
'error' => $request->session()->get('error'),
],
]);
}
}php
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'auth' => [
'user' => $request->user() ? [
'id' => $request->user()->id,
'name' => $request->user()->name,
'email' => $request->user()->email,
] : null,
],
'flash' => [
'success' => $request->session()->get('success'),
'error' => $request->session()->get('error'),
],
]);
}
}Flash Messages Component
闪存消息组件
vue
<!-- resources/js/Components/FlashMessages.vue -->
<script setup lang="ts">
import { usePage } from '@inertiajs/vue3'
import { computed, ref, watch } from 'vue'
const page = usePage()
const flash = computed(() => page.props.flash as { success?: string; error?: string })
const visible = ref(false)
let timer: ReturnType<typeof setTimeout> | null = null
watch(flash, (newFlash) => {
if (newFlash.success || newFlash.error) {
visible.value = true
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
visible.value = false
}, 3000)
}
}, { deep: true })
</script>
<template>
<div v-if="visible" class="fixed top-4 right-4 z-50">
<div
v-if="flash.success"
class="bg-green-500 text-white px-4 py-2 rounded shadow"
>
{{ flash.success }}
</div>
<div
v-if="flash.error"
class="bg-red-500 text-white px-4 py-2 rounded shadow"
>
{{ flash.error }}
</div>
</div>
</template>vue
<!-- resources/js/Components/FlashMessages.vue -->
<script setup lang="ts">
import { usePage } from '@inertiajs/vue3'
import { computed, ref, watch } from 'vue'
const page = usePage()
const flash = computed(() => page.props.flash as { success?: string; error?: string })
const visible = ref(false)
let timer: ReturnType<typeof setTimeout> | null = null
watch(flash, (newFlash) => {
if (newFlash.success || newFlash.error) {
visible.value = true
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
visible.value = false
}, 3000)
}
}, { deep: true })
</script>
<template>
<div v-if="visible" class="fixed top-4 right-4 z-50">
<div
v-if="flash.success"
class="bg-green-500 text-white px-4 py-2 rounded shadow"
>
{{ flash.success }}
</div>
<div
v-if="flash.error"
class="bg-red-500 text-white px-4 py-2 rounded shadow"
>
{{ flash.error }}
</div>
</div>
</template>How to Use
使用方法
Read individual rule files for detailed explanations and code examples:
rules/form-useform-basic.md
rules/page-props-typing.md
rules/layout-persistent.md阅读单个规则文件获取详细说明和代码示例:
rules/form-useform-basic.md
rules/page-props-typing.md
rules/layout-persistent.mdProject Structure
项目结构
laravel-inertia-vue/
├── SKILL.md # This file - overview and examples
├── README.md # Quick reference guide
├── AGENTS.md # Integration guide for AI agents
├── metadata.json # Skill metadata and references
└── rules/
├── _sections.md # Rule categories and priorities
├── _template.md # Template for new rules
├── page-*.md # Page component patterns (6 rules)
├── form-*.md # Form handling patterns (8 rules)
├── nav-*.md # Navigation patterns (5 rules)
├── shared-*.md # Shared data patterns (4 rules)
└── layout-*.md # Layout patterns (1 rule)laravel-inertia-vue/
├── SKILL.md # 本文档 - 概览与示例
├── README.md # 快速参考指南
├── AGENTS.md # AI集成指南
├── metadata.json # 技能元数据与参考链接
└── rules/
├── _sections.md # 规则类别与优先级
├── _template.md # 新规则模板
├── page-*.md # 页面组件模式(6条规则)
├── form-*.md # 表单处理模式(8条规则)
├── nav-*.md # 导航模式(5条规则)
├── shared-*.md # 共享数据模式(4条规则)
└── layout-*.md # 布局模式(1条规则)References
参考资料
- Inertia.js Documentation - Official Inertia.js docs
- Laravel Documentation - Laravel framework docs
- Vue 3 Documentation - Official Vue 3 docs
- Ziggy - Laravel route helper for JavaScript
- Inertia.js Documentation - Inertia.js官方文档
- Laravel Documentation - Laravel框架官方文档
- Vue 3 Documentation - Vue 3官方文档
- Ziggy - Laravel路由的JavaScript工具
License
许可证
MIT License. This skill is provided as-is for educational and development purposes.
MIT许可证。本技能仅用于教育和开发目的,按现状提供。
Metadata
元数据
- Version: 1.0.0
- Last Updated: 2026-03-17
- Maintainer: Asyraf Hussin
- Rule Count: 24 rules across 6 categories
- Tech Stack: Laravel 12+, Inertia.js 2.0+, Vue 3.3+, TypeScript 5+
- 版本: 1.0.0
- 最后更新: 2026-03-17
- 维护者: Asyraf Hussin
- 规则数量: 6个类别共24条规则
- 技术栈: Laravel 12+, Inertia.js 2.0+, Vue 3.3+, TypeScript 5+