Laravel 13 前端集成完全指南

Laravel 13 提供了现代化的前端集成方案,支持 Vite 构建工具,并与 Vue、React 等主流前端框架无缝集成。本文将深入探讨前端开发的最佳实践。

Vite 集成

安装与配置

1
2
3
4
5
6
# 安装 Laravel Breeze(包含 Vite 配置)
composer require laravel/breeze --dev
php artisan breeze:install

# 或手动安装 Vite
npm install --save-dev vite laravel-vite-plugin

Vite 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/js/app.js',
],
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
resolve: {
alias: {
'@': '/resources/js',
'ziggy-js': '/vendor/tightenco/ziggy/dist/index.js',
},
},
server: {
hmr: {
host: 'localhost',
},
},
});

Blade 中使用 Vite

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<div id="app"></div>
</body>
</html>

环境配置

1
2
3
4
# .env
VITE_APP_NAME="${APP_NAME}"
VITE_API_URL="${APP_URL}/api"
VITE_STRIPE_KEY="${STRIPE_KEY}"
1
2
const appName = import.meta.env.VITE_APP_NAME;
const apiUrl = import.meta.env.VITE_API_URL;

Vue.js 集成

安装 Vue

1
2
npm install --save-dev vue @vitejs/plugin-vue
npm install vue-router@4 pinia axios

Vue 应用入口

1
2
3
4
5
6
7
8
9
10
11
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import router from './router';
import App from './App.vue';

const app = createApp(App);

app.use(createPinia());
app.use(router);

app.mount('#app');

Vue Router 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createRouter, createWebHistory } from 'vue-router';
import Home from '@/pages/Home.vue';
import Users from '@/pages/Users.vue';

const routes = [
{ path: '/', name: 'home', component: Home },
{ path: '/users', name: 'users', component: Users },
{
path: '/users/:id',
name: 'users.show',
component: () => import('@/pages/UserShow.vue'),
props: true,
},
];

export default createRouter({
history: createWebHistory(),
routes,
});

Pinia 状态管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useUserStore = defineStore('user', () => {
const users = ref([]);
const loading = ref(false);

const userCount = computed(() => users.value.length);

async function fetchUsers() {
loading.value = true;
try {
const { data } = await axios.get('/api/users');
users.value = data;
} finally {
loading.value = false;
}
}

return { users, loading, userCount, fetchUsers };
});

Vue 组件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<div class="user-list">
<div v-if="loading" class="loading">加载中...</div>

<div v-else>
<div v-for="user in users" :key="user.id" class="user-card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<router-link :to="{ name: 'users.show', params: { id: user.id } }">
查看详情
</router-link>
</div>
</div>
</div>
</template>

<script setup>
import { onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/stores/user';

const userStore = useUserStore();
const { users, loading } = storeToRefs(userStore);

onMounted(() => {
userStore.fetchUsers();
});
</script>

<style scoped>
.user-card {
border: 1px solid #ddd;
padding: 1rem;
margin-bottom: 1rem;
}
</style>

React 集成

安装 React

1
2
npm install --save-dev react react-dom @vitejs/plugin-react
npm install react-router-dom zustand axios

React 应用入口

1
2
3
4
5
6
7
8
9
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

createRoot(document.getElementById('app')).render(
<BrowserRouter>
<App />
</BrowserRouter>
);

React Router 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import Users from './pages/Users';
import UserShow from './pages/UserShow';

export default function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users" element={<Users />} />
<Route path="/users/:id" element={<UserShow />} />
</Routes>
);
}

Zustand 状态管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { create } from 'zustand';

const useUserStore = create((set, get) => ({
users: [],
loading: false,

fetchUsers: async () => {
set({ loading: true });
try {
const { data } = await axios.get('/api/users');
set({ users: data });
} finally {
set({ loading: false });
}
},

getUserById: (id) => {
return get().users.find(user => user.id === parseInt(id));
},
}));

export default useUserStore;

React 组件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { useEffect } from 'react';
import { Link } from 'react-router-dom';
import useUserStore from '../stores/user';

export default function Users() {
const { users, loading, fetchUsers } = useUserStore();

useEffect(() => {
fetchUsers();
}, []);

if (loading) {
return <div>加载中...</div>;
}

return (
<div className="user-list">
{users.map(user => (
<div key={user.id} className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<Link to={`/users/${user.id}`}>查看详情</Link>
</div>
))}
</div>
);
}

API 集成

Axios 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import axios from 'axios';

const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
});

api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}

const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
if (csrfToken) {
config.headers['X-CSRF-TOKEN'] = csrfToken;
}

return config;
});

api.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);

export default api;

API 服务封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export const userService = {
async all() {
const { data } = await api.get('/users');
return data;
},

async find(id) {
const { data } = await api.get(`/users/${id}`);
return data;
},

async create(payload) {
const { data } = await api.post('/users', payload);
return data;
},

async update(id, payload) {
const { data } = await api.put(`/users/${id}`, payload);
return data;
},

async delete(id) {
await api.delete(`/users/${id}`);
},
};

Ziggy 路由集成

安装 Ziggy

1
2
composer require tightenco/ziggy
npm install ziggy-js

配置 Ziggy

1
2
3
4
5
6
7
8
// config/ziggy.php
return [
'except' => [
'debugbar.*',
'telescope.*',
'horizon.*',
],
];

Blade 中引入

1
2
3
@routes
{{-- 或指定路由组 --}}
@routes('api')

前端使用

1
2
3
4
5
6
7
import { route } from 'ziggy-js';

const url = route('users.show', { user: 1 });
const urlWithQuery = route('users.index', { page: 2, sort: 'name' });

const current = route().current();
const isActive = route().current('users.*');

Inertia.js 集成

安装 Inertia

1
2
composer require inertiajs/inertia-laravel
npm install @inertiajs/vue3

配置 Inertia

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// AppServiceProvider.php
use Inertia\Inertia;

public function boot(): void
{
Inertia::share([
'appName' => config('app.name'),
'auth' => fn() => [
'user' => auth()->user(),
],
'flash' => fn() => [
'success' => session('success'),
'error' => session('error'),
],
]);
}

控制器返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use Inertia\Inertia;

class UserController extends Controller
{
public function index()
{
return Inertia::render('Users/Index', [
'users' => User::all(),
'filters' => request()->only(['search', 'role']),
]);
}

public function store(Request $request)
{
User::create($request->validated());

return redirect()->route('users.index')
->with('success', '用户创建成功');
}
}

Vue 页面组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<Layout>
<Head title="用户列表" />

<div class="mb-4">
<input v-model="form.search" placeholder="搜索用户..." />
</div>

<div v-for="user in users" :key="user.id">
{{ user.name }}
</div>

<Link href="/users/create" class="btn">创建用户</Link>
</Layout>
</template>

<script setup>
import { Link, Head } from '@inertiajs/vue3';
import Layout from '@/Layouts/Layout.vue';
import { useForm } from '@inertiajs/vue3';

const props = defineProps({
users: Array,
filters: Object,
});

const form = useForm({
search: props.filters.search || '',
});
</script>

Livewire 集成

安装 Livewire

1
composer require livewire/livewire

创建组件

1
php artisan make:livewire UserList

Livewire 组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

namespace App\Livewire;

use Livewire\Component;
use App\Models\User;

class UserList extends Component
{
public $search = '';
public $role = '';

protected $queryString = ['search', 'role'];

public function render()
{
$users = User::query()
->when($this->search, fn($q) => $q->where('name', 'like', "%{$this->search}%"))
->when($this->role, fn($q) => $q->where('role', $this->role))
->get();

return view('livewire.user-list', compact('users'));
}

public function delete($id)
{
User::destroy($id);
session()->flash('success', '用户已删除');
}
}

Livewire 视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div>
<input type="text" wire:model.live.debounce.300ms="search" placeholder="搜索...">

<select wire:model="role">
<option value="">所有角色</option>
<option value="admin">管理员</option>
<option value="user">用户</option>
</select>

@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif

<ul>
@foreach($users as $user)
<li>
{{ $user->name }}
<button wire:click="delete({{ $user->id }})" wire:confirm="确定删除?">
删除
</button>
</li>
@endforeach
</ul>
</div>

Tailwind CSS 集成

安装 Tailwind

1
2
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Tailwind 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default {
content: [
'./resources/**/*.blade.php',
'./resources/**/*.js',
'./resources/**/*.vue',
],
theme: {
extend: {
colors: {
primary: '#3490dc',
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
};

CSS 入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
.btn {
@apply px-4 py-2 rounded-lg font-semibold transition-colors;
}

.btn-primary {
@apply btn bg-primary text-white hover:bg-primary-dark;
}
}

总结

Laravel 13 的前端集成提供了:

  • 现代化的 Vite 构建工具
  • Vue.js 和 React 的完整支持
  • Inertia.js 单页应用方案
  • Livewire 全栈组件方案
  • Tailwind CSS 样式框架
  • Ziggy 路由共享

选择适合项目需求的前端方案,可以大大提高开发效率。