导读:本期聚焦于小伙伴创作的《Laravel实战教程:构建电影排名表单与实现数据排序功能的完整步骤》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《Laravel实战教程:构建电影排名表单与实现数据排序功能的完整步骤》有用,将其分享出去将是对创作者最好的鼓励。

Laravel 中创建排名表单并实现数据排序

在Web应用开发中,实现一个允许用户对项目(如产品、文章或候选人)进行主观排名的系统是常见的需求。Laravel框架以其优雅的语法和强大的功能,为构建此类功能提供了坚实的基础。本文将详细介绍如何在Laravel应用中创建一个排名表单,并实现后端的数据排序逻辑。

一、 数据库准备与模型创建

首先,我们需要一个数据表来存储待排名的项目。假设我们正在构建一个“最佳电影”排名系统。

1.1 创建数据迁移

使用Artisan命令生成迁移文件:

php artisan make:migration create_movies_table

编辑生成的迁移文件,定义表结构:

<?php

use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('movies', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('description')->nullable();
            // `rank` 字段将存储用户给出的排名,初始可为null
            $table->integer('rank')->nullable()->unique();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('movies');
    }
};

运行迁移以创建表:

php artisan migrate

1.2 创建Eloquent模型

生成并编辑Movie模型:

php artisan make:model Movie
<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Movie extends Model
{
    use HasFactory;

    protected $fillable = ['title', 'description', 'rank'];

    /**
     * 根据排名升序获取电影的作用域。
     */
    public function scopeOrderByRank($query)
    {
        return $query->orderBy('rank');
    }

    /**
     * 获取尚未被排名的电影的作用域。
     */
    public function scopeUnranked($query)
    {
        return $query->whereNull('rank');
    }
}

二、 创建排名表单视图

表单是用户交互的界面。我们将创建一个Blade视图,允许用户为一系列电影分配排名(例如,从1到5)。

2.1 基本表单结构

创建视图文件 resources/views/rankings/create.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rank Movies</title>
    <link href="https://www.ipipp.com/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container mt-5">
        <h1>Rank Your Top Movies</h1>
        <p>请为以下电影分配唯一的排名(数字越小,排名越高)。</p>

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

        <form action="{{ route('rankings.store') }}" method="POST">
            @csrf
            <div class="list-group mb-3">
                @forelse($movies as $movie)
                    <div class="list-group-item">
                        <div class="row">
                            <div class="col-md-8">
                                <h5>{{ $movie->title }}</h5>
                                <p class="text-muted">{{ $movie->description }}</p>
                            </div>
                            <div class="col-md-4">
                                <label for="rank_{{ $movie->id }}" class="form-label">排名</label>
                                <input type="number"
                                       name="ranks[{{ $movie->id }}]"
                                       id="rank_{{ $movie->id }}"
                                       class="form-control rank-input"
                                       min="1"
                                       max="{{ $movies->count() }}"
                                       value="{{ old('ranks.' . $movie->id, $movie->rank) }}"
                                       placeholder="输入1-{{ $movies->count() }}之间的数字">
                                @error('ranks.' . $movie->id)
                                    <div class="text-danger small">{{ $message }}</div>
                                @enderror
                            </div>
                        </div>
                    </div>
                @empty
                    <div class="list-group-item">暂无电影可供排名。</div>
                @endforelse
            </div>
            <button type="submit" class="btn btn-primary">提交排名</button>
        </form>

        <hr>
        <h3>当前排名榜</h3>
        <ul class="list-group">
            @foreach($rankedMovies as $movie)
                <li class="list-group-item d-flex justify-content-between align-items-center">
                    {{ $movie->title }}
                    <span class="badge bg-primary rounded-pill">第 {{ $movie->rank }} 名</span>
                </li>
            @endforeach
        </ul>
    </div>

    <script src="https://www.ipipp.com/js/bootstrap.bundle.min.js"></script>
    <script>
        // 简单的客户端验证:确保排名值唯一
        document.querySelector('form').addEventListener('submit', function(e) {
            const inputs = document.querySelectorAll('.rank-input');
            const values = Array.from(inputs).map(input => input.value).filter(v => v);
            const uniqueValues = new Set(values);
            if (values.length !== uniqueValues.size) {
                e.preventDefault();
                alert('错误:排名数字不能重复!');
            }
        });
    </script>
</body>
</html>

三、 构建控制器处理逻辑

控制器负责处理表单提交的请求,验证数据,并更新数据库。

3.1 生成控制器

php artisan make:controller RankingController

3.2 编写控制器方法

编辑 app/Http/Controllers/RankingController.php

<?php

namespace AppHttpControllers;

use AppModelsMovie;
use IlluminateHttpRequest;
use IlluminateSupportFacadesDB;
use IlluminateValidationRule;

class RankingController extends Controller
{
    /**
     * 显示排名表单。
     */
    public function create()
    {
        // 获取所有电影,已排名的和未排名的分开
        $movies = Movie::orderBy('rank', 'asc')->get();
        $rankedMovies = $movies->whereNotNull('rank');
        $unrankedMovies = $movies->whereNull('rank');

        // 为了表单显示,合并它们,但未排名的在后面
        $moviesForForm = $rankedMovies->merge($unrankedMovies);

        return view('rankings.create', [
            'movies' => $moviesForForm,
            'rankedMovies' => $rankedMovies
        ]);
    }

    /**
     * 处理排名表单提交。
     */
    public function store(Request $request)
    {
        // 1. 基础验证:确保提交的数据是数组且数量正确
        $validated = $request->validate([
            'ranks' => 'required|array',
            'ranks.*' => [
                'nullable',
                'integer',
                'min:1',
                // 自定义规则验证排名唯一性
                function ($attribute, $value, $fail) use ($request) {
                    if ($value !== null) {
                        $count = collect($request->input('ranks'))
                            ->filter(fn($rank) => $rank == $value)
                            ->count();
                        if ($count > 1) {
                            $fail("排名 {$value} 被重复使用了。");
                        }
                    }
                },
            ],
        ]);

        // 2. 使用数据库事务确保数据一致性
        DB::transaction(function () use ($validated) {
            // 首先,重置所有电影的排名(根据业务逻辑可选)
            // Movie::query()->update(['rank' => null]);

            // 然后,为每个提交了排名的电影更新排名
            foreach ($validated['ranks'] as $movieId => $rank) {
                if (!is_null($rank)) {
                    Movie::where('id', $movieId)->update(['rank' => (int) $rank]);
                }
            }

            // 3. (可选)处理排名间隙
            $this->compressRanks();
        });

        return redirect()->route('rankings.create')
                         ->with('success', '排名已成功更新!');
    }

    /**
     * 压缩排名,确保排名是连续的(例如:1,2,3...而不是1,3,4)。
     * 这是一个可选的高级功能。
     */
    private function compressRanks()
    {
        $rankedMovies = Movie::whereNotNull('rank')->orderBy('rank', 'asc')->get();
        $expectedRank = 1;
        foreach ($rankedMovies as $movie) {
            if ($movie->rank != $expectedRank) {
                $movie->update(['rank' => $expectedRank]);
            }
            $expectedRank++;
        }
    }
}

四、 定义路由

routes/web.php 文件中添加以下路由。

<?php

use AppHttpControllersRankingController;
use IlluminateSupportFacadesRoute;

Route::get('/rankings', [RankingController::class, 'create'])->name('rankings.create');
Route::post('/rankings', [RankingController::class, 'store'])->name('rankings.store');

五、 实现高级排序与查询

基本的排名存储完成后,我们经常需要根据排名进行数据检索和展示。

5.1 使用查询作用域

我们已经在Movie模型中定义了 scopeOrderByRank 作用域。现在可以方便地使用它:

// 获取按排名升序排列的所有电影
$movies = Movie::orderByRank()->get();

// 获取前三名
$topThree = Movie::whereNotNull('rank')
                 ->orderBy('rank', 'asc')
                 ->limit(3)
                 ->get();

// 获取尚未排名的电影
$unranked = Movie::unranked()->get();

5.2 在视图中展示排序结果

创建一个新的视图来展示最终的排名榜,例如 resources/views/rankings/index.blade.php

<h2>最终电影排名榜</h2>
<table class="table table-striped">
    <thead>
        <tr>
            <th scope="col">名次</th>
            <th scope="col">电影名称</th>
            <th scope="col">描述</th>
        </tr>
    </thead>
    <tbody>
        @foreach($movies as $movie)
        <tr>
            <th scope="row">{{ $movie->rank }}</th>
            <td><strong>{{ $movie->title }}</strong></td>
            <td>{{ $movie->description }}</td>
        </tr>
        @endforeach
    </tbody>
</table>

六、 总结与扩展

通过以上步骤,我们在Laravel中完整实现了一个具备表单提交、数据验证、唯一性检查、数据库更新和结果展示的排名系统。核心要点包括:

  • 使用 <input type="number"> 元素构建排名表单。

  • 通过Laravel的表单请求验证和自定义验证规则确保排名数据的有效性(如唯一性)。

  • 利用数据库事务(DB::transaction)保证多条排名更新操作的原子性。

  • 在Eloquent模型中使用查询作用域来封装常用的排序逻辑。

此系统可以轻松扩展:

  • 多用户排名:将 rank 字段移至一个名为 user_movie_rankings 的中间表,并关联用户ID。

  • 加权排名:增加一个“权重”或“投票数”字段,最终排名根据算法(如加权平均)计算得出。

  • 动态排名更新:使用Livewire或Alpine.js实现拖拽排序,并通过AJAX实时保存排名,提升用户体验。

  • 排名历史:创建排名历史记录表,追踪每次排名的变化。

通过灵活运用Laravel的MVC架构和Eloquent ORM,开发者可以高效地构建出复杂且健壮的排名与排序功能。

Laravel排名系统 表单创建 数据排序 Eloquent模型 数据库事务

免责声明:已尽一切努力确保本网站所含信息的准确性。网站部分内容来源于网络或由用户自行发表,内容观点不代表本站立场。本站是个人网站免费分享,内容仅供个人学习、研究或参考使用,如内容中引用了第三方作品,其版权归原作者所有。若内容触犯了您的权益,请联系我们进行处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。前端、网络、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握网站开发与运维所需的核心技术栈。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端逻辑,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。