laravel4.2 新手入门福利1

Laravel研究 专栏收录该内容
3 篇文章 0 订阅

文章简介

这片文章是很久之前写的一篇文章,现在发布出来作为测试markdown用的。
此文使用laravel4.2环境,年代久远了啊。
以下为原文:

翻译简介

此文为Getting Started With Laravel 4,chapter 3翻译

最近研究了一下php的流行框架laravel,相信很多朋友也一直在研究,下面是getting started with laravel 4中的第三章翻译,通过这章初学者可以掌握Laravel 4的routes,database,blade,Eloquent基本用法,基本理解model的概念。

下面是我的源代码地址:https://github.com/xsmyqf/startlaravel.git

由于篇幅较长,译者水平有限,难免会有一些错误,而且一些翻译也不地道,希望大家指正,如果在本文中有一些错误或者有一些翻译方面的建议,请留言在评论区。如果您在做的过程中程序出现错误,可以下载例示程序纠察自己错误。

本章摘要

在已经学习了laravel的通俗约定和仓库, 并且您已经知道如何用composer去创建一个新的项目,你现在准备去创建你的第一个应用。
在这章里,你通过一个实用的方法实践一下前两章的一些概念,你需要做的如下:

1.概述(Sketch out)URLs和你的应用的实体。
2.解决在你开始的时候遇到一些普通问题。
3.定义路由也就是他们的action和models之间的关系。
4.准备你的数据库然后学习如何用Eloquent操作它。
5.用Blade模板语言去创建分层(hierarchical)布局。

第一步去创建一个web应用是去识别并且定义它的要求。然后,一旦这个主要特性被构想(formulated)出来,我们得到主要的实体和应用的URL结构。明确定义要求和URLs的集合对其他任务比如测试来说至关重要;这在本书中会晚点涉及。

概括应用

我们要建立一个可以浏览的关于猫信息的数据库,登陆者可以有能力为他们的猫创建页面然后填入基本信息,比如名字,出生日期,还有每只猫的后代。这些信息支持默认的Create-RetrieveUpdate-Delete操作(CRUD)。我们会创建一个可以通过后代选项过滤猫信息的总览页面。所有关于安全的,比如身份验证和权限特性是有意的(intentionally)被放在一边,因为他们会在下一章被涉及。

实体,关系和属性

首先,我们需要去定义我们应用的实体,在广泛意义上(In broad terms),一个实体是一个应用应该存储的数据有关的东西(人,地方,或者对象)。

关于表的要求,我们可以提取如下实体和属性:

猫,有一个数值标识主键,一个名字,一个出生日期,和种类。

种类,应该只有一个标识主键,和一个名字。

这个信息会帮你定义数据表用来存储实体,关系和属性,也就是模型,用来展示数据库里的对象的php类。

我们应用的分布地图

在这里插入图片描述
在这里插入图片描述
我们现在需要考虑应用关于URL的结构。有清晰和富有表达力的URLs有很多好处。在实用性层面( On a usability level),用户会更容易接受和更有信心。对于经常使用的用户,单独的页面会更容易去记住或者设立书签,如果他们包含相关的关键字,他们会经常在搜索引擎的排列前茅。

为了达到最初的要求(To fulfill the initial set of requirements),我们将需要如下的路由在我们的应用中。
​​
我们稍后会简短的介绍laravel如何帮我们去把这些路由转变成(sketch into)真实的代码。如果你已经写过php程序,你会知道如何实现这样的路由结构。为了让你有更多的认识( To add some perspective),这就是倒数第二个URl特别像一个传统php脚本(不用URL重写):/index.php?p=cats&id=1&_ action=delete&confirm=true。

之前的表 可以用一个笔和一张纸,或者一个电子表格,甚至你喜欢的代码编辑器里用ASCII字符准备。在开发初始的阶段,路由表是一个非常重要的让你首先思考URLs的原形设计工具,它会帮你反复定义或者提炼你应用的结构。

如果你已经接触过REST APIs,你应该很熟悉这种类型的路由结构。在RESTFUL项目里,我们有一个反应给不同HTTP动词的猫的资源,然后提供一些附加的路由集合去显示必要的表格。

然而如果你没接触过RESTful sites,这个HTTP的put和delete方法概念对你来说可能很新。即使网页浏览器不支持这些方法对于标准的HTTP请求,laravel用了一项(其他框架比如Rails)用的技术,然后通过一个_method输入字段给一个表格来模仿这些方法。这样(this way),他们通过一个标准的POST请求被发送,然后被委派正确的路由给控制器方法在一个应用里。

注意,没有一个表的提交方式使用get处理的。这主要是因为他们是有副作用的(side effect),一个用户同样可以意外的多次触发相同的动作当用这个浏览器历史的时候。因此,当他们被调用时,这些路由从来不给用户显示任何信息。反而,直接将它们引导到那个界面在完成这个动作后。(比如,delete /cats/:id会直接将用户引导到get /cats)

开始一个应用

现在我们已经构建好应用的蓝图,让我们卷起袖子然后开始写一些代码(Let‘s roll up our sleeves and start writing some code)。

写第一个路由

Route::get('/', function(){
	return "All cats";
});
Route::get('cats/{id}', function($id){
	return "Cat #$id";
});

get的第一个参数是URI表达式,当任何一个地址参数符合表达式,第二个参数的匿名函数就会被执行。注意前置“/”(slash prefix)在URI表达式中是可选的;然而,你不应该有任何尾后的“/”。你可以通过打开你的web浏览器访问http://localhost:8000/cats/123来确定你的路由是否工作。

限制路由的参数

在第二个路由的URI参数中,{id}可以匹配任何字符和数字,去限制它让它只能匹配数字,我们可以像下面在路由中chain一个where方法:

Route::get('cats/{id}', function($id){
	return "Cat #$id";
})->where('id', '[0-9]+');

这个where方法有两个参数:第一个是参数名,第二个是它需要匹配的正则表达式模式。

{关于链式操作:

php5.5的链需要这样

new ojb() )->talk() ) ->func();

每个方法返回$this对象本身就实现链式操作了
}

抓住丢失的路由

你可以抓住一个“Not Found“异常然后显示一个自己定制的信息通过在app/start/global.php中定义一个如下的missing方法给你的应用:

App::missing(function($exception){
	return Response::make("Page not found", 404);
});

在这我们并不仅仅返回一个字符串,一个response对象有两个参数,第一个参数是要显示的消息,第二个参数是HTTP状态码。

在刚开始我们写的两个路由中,Laravel会自动将字符串转换成一个HTTP状态码为200的HTTP resonse(比如,在第一个路由中它是Response::make(“All cats”, 200))。尽管对用户来说区别可能并不明显,对于获取你网站的搜索引擎或者当你想写一个API时,”404 Not Found“和"200 OK"之间的区别就十分重要了。

处理重定向

通过在你的路由中返回一个Redirect对象可以将访问者重定向。比如,我们希望每个人去被重定向到/cats当它们第一次访问应用,我们会写下面几行代码:

Route::get('/', function(){
	return Redirect::to('cats');
});
Route::get('cats', function(){
	return "All cats";
});

返回视图

你经常从你的路由中返回的对象是view对象。views从一个路由(或者一个控制器)接收数据,然后将它注入到一个模板,从而,在你的应用中帮你将业务和显示逻辑分开。

去添加你的第一个视图,创建一个about.php文件名在app/views然后将下面的内容添加到里面:

<h2>About this site</h2>
There are over <?php echo $number_of_cats; ?> cats on this site!

然后用view::make方法返回具有一个变量$number_of_cats的视图:

Route::get('about', function(){
	return View::make('about')->with('number_of_cats', 9000);
});

最后,访问/about在你的浏览器去看被提供的视图。这个视图是用纯php写的;然而,Laravel有一个叫Blade的非常强大的模板语言,将会在这章中晚点介绍。

准备数据库

在我们展开我们的路由函数之前,我们需要去定义我们应用的模型,准备必要的数据库表,然后用一些初始数据填充数据库。为了保持事情的简洁和展示Eloquent ORM的灵活性,我们准备去用SQLite,一个轻量级基于文件的数据库。

去配置Laravel用SQLite,打开app/config/datebase.php然后改变默认的数据库连接名将mysql改成sqlite。保证默认的数据文件,app/datebase/production.sqlite,存在。

创建Eloquent模型

这第一步也就是最早的一步是去定义我们应用将要去交互的模型。在本章的开始,我们确定有两个主要的实体,cats和breeds。Laravel包含Eloquent,一个强大ORM可以帮助你定义这些实体并将它们映射到相应的数据库表中,然后和他们用php方法,而不是用原始的(raw)SQL交互。按照惯例,他们被写到一个单独的表;一个称为cat的模型将会映射到数据库里的cats表上,一个假想的(hypothetical)mouse模型会映射到mice上。

这个cat模型,保存在app/models/cat.php里,将会有一个属于breed模型的关系,在下面的小代码段中定义:

class Cat extends Eloquent {
	protected $fillable =array('name','date_of_birth','breed_id');
	public function breed(){
	return $this->belongsTo('Breed');
	}
}

这个$fillable数组定义了一组laravel可以用mass assignment(传递整个model对象属性参数,一个将属性分配给一个模型的简单方法)填充的字段。通常,在数据库中,laravel用来寻找相关模型的字段叫breed_id。这个Breed模型,在app/models/bread.php中通过如下(与belongsto相反的hasmany关系),被定义:

class Breed extends Eloquent {
	public $timestamps = false;
	public function cats(){
	return $this->hasMany('Cat');
}
}

默认的,laravel会有一个created_at和一个updated_at时间戳域在数据库表中。因为我们并不在breeds里保存这些时间戳感兴趣,我们可以通过将$timestamps设置成false来禁用他们。

现在这些就是我们在我们的模型中所需要的全部代码。随着进步了解,我们会发现各种Eloquent特性;然而,在这章中,我们将主要用两种方法:all()和find()。为了表明他们的含义(To illustratetheir purpose),下面是些他们生成的SQL查询:

Breed::all() => SELECT * FROM breeds;
Cat::find(1) => SELECT * FROM cats WHERE id = 1;

在我们应用里的视图和控制器中,一个Eloquent模型的属性可以用->操作符来检索,比如: c a t − &gt; n a m e 。 同 样 也 适 用 ( g o f o r ) 相 关 模 型 的 属 性 , 比 如 可 以 这 样 被 获 得 : cat-&gt;name。同样也适用(go for)相关模型的属性,比如可以这样被获得: cat>namegoforcat->breed->name,在这个现象的后面,Eloquent将执行必要的SQL连接。

创建数据库的表

现在既然(Now that )我们已经定义了我们的模型,我们需要去创造相应的数据库表。多亏Laravel的对迁移的支持和强大的建表器,你将不会写任何SQL代码而且你将有能力去记录(keep track of)任何表的改变在版本控制系统中。为了创建你的第一个迁移,请打开控制端,然后键入如下命令:

php artisan migrate:make add_cats_and_breeds_table

这将会创建一个新的迁移在app/database/migrations/里,如果你打开最近新创建的文件你会发现一些laravel自动为你生成的代码。迁移常常有一个定义表变化的up()和down()方法当migrating up and down时。按照惯例,表名和域名被写成“snake_case”。更多的,表名被写成复数形式(plural form)。

我们第一个迁移将会像下面这样:

public function up(){
Schema::create('cats', function($table){
	$table->increments('id');
	$table->string('name');
	$table->date('date_of_birth');
	$table->integer('breed_id')->nullable();
	$table->timestamps();
});
Schema::create('breeds', function($table){
	$table->increments('id');
	$table->string('name');
});
}
public function down(){
	Schema::drop('cats');
	Schema::drop('breeds');
}

这个date()和string()方法创建相对应类型的域(在这个例子中,是date和varchar)在数据库中,increments()创建一个自动增长的整数主键,然后timestamps()增加created_at和updated_at Eloquent默认会增加的时间域,nullable()方法会执行哪列使用null值。

去运行这个数据库迁移,请输入如下指令:

$ php artisan migrate

当它第一次运行,这个命令也会创建一个migrations表,在这里laravel用它去记录要运行的迁移。

填充数据库

我们可以用laravel提供的seeding helpers,而不用手动填充数据库。这次,没有用Artisan命令生成的文件,但是我们都需要做的是在app/database/seeds/目录下创建一个名为 BreedsTableSeeder.php的类,这个类继承laravel的seeder class 和一个run()方法:

class BreedsTableSeeder extends Seeder {
public function run(){
	DB::table('breeds')->insert(array(
		array('id'=>1, 'name'=>"Domestic"),
		array('id'=>2, 'name'=>"Persian"),
		array('id'=>3, 'name'=>"Siamese"),
		array('id'=>4, 'name'=>"Abyssinian"),
		));
	}
}

你可以用插入一个数组来复制一个数据文件到数据库表或者视图中(bulk insert),但你也可以在run()方法里插入任意代码去加载从CSV或者JSON文件里的数据,那也有很多可以帮助你生成大量测试数据来填充你的数据库的库。

为了控制seeders的执行顺序,Laravel可以让你单独地调用他们在app/database/seeds/DatabaseSeeder.php中。在我们的情况里,因为我们仅仅只需要一个seeder,所有我们需要写的是下面这行:

$this->call('BreedsTableSeeder');

然后,我们可以通过调用如下命令来填充数据库:

$ php artisan db:seed

像上边在databaseseeder.php中注册,然后使用php artisan db:seed是一种方法。

第二种方式是php artisan db:seed --class=指定类,不过这种方式只能一次指定一个类。

掌握Blade

现在我们在数据库中已经有一些信息,我们需要定义去显示他们的模板。Blade是Laravel的轻量级模板语言而且它的语法简单易学。这有一些例子表明Blade如何减少键击次数并且增加模板的可读性:
在这里插入图片描述

如果你信任用户并且或者有个值需要返回,你应该只用双大括号{{double brace}}去输出一个变量。在所有其他情况下(In all other cases),确保你用了3对大括号去阻止XSS vulnerabilities(将会在下章解释的更具体点)。

Blade支持所有的PHP主要条件语句和循环语句:@for,@foreach,@while,@if,@elseif;因此,从而让你在你模板的里不用繁琐地使用< ?php。

创建一个新的master view

Blade允许你去创建分层(hierarchical)布局通过将模板嵌套和延伸。下面的这段代码是我们将在我们应用中使用的“master”模板。我们将它保存在app/views/master.blade.php。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Cats DB</title>
<link rel="stylesheet" href="{{asset('bootstrap-3.0.0.min.
css')}}">
</head>
<body>
<div class="container">
<div class="page-header">
@yield('header')
</div>
@if(Session::has('message'))
<div class="alert alert-success">
{{Session::get('message')}}
</div>
@endif
@yield('content')
</div>
</body>
</html>

我们包含Bootstrap 3 CSS框架去加快网页应用的界面原形。你可以从getbootstrap官网下载然后包含具体的CSS文件在public/folder。为了保证你的路径前缀被设置正确,甚至可以在laravel一个子文件夹中运行,我们使用asset()帮助器。在下面的模板中,我们将用其他帮助器比如url和link_to,它们可以帮我们写更多的简洁的代码而且从任何HTML实体中分离。如果想要看到更多对你有用的完整的Blade模板帮助器的列表,请访问:
http://laravel.com/docs/helpers

为了通知用户关于某个动作的结果,我们在header和页面内容的中间准备了一个通告区域。这个短的数据(换句话说,session数据只对下一次请求有效)是通过session对象传递和获取。

这个@yield指令作为不同部分的(子视图可以被放置和重写的)占位符。为了理解一个子模板如何重用他们,我们将通过把他的扩展名改成.blade.php然后扩展我们的master模板来重新创建这个About视图:

@extends('master')
@section('header')
<h2>About this site</h2>
@stop
@section('content')
<p>There are over {{$number_of_cats}} cats on this site!</p>
@stop

这个@section…@stop指令限定了将要被注入到master模板内容块的范围。你可以从下边这张例图中看出来是如何做的:
在这里插入图片描述
如果你在你的网页浏览器中重新打开/about路由,不用改变任何你先前定义的路由,你将会看到新的视图。因为新文件的扩展名是.blade.php。Laravel的视图寻找器将会把它当作Blade模板来使用。

继续讨论路由

现在既然我们有了一个可以扩展,复用的主模板,我们可以开始去创建应用的每个路由在app/routes.php,并会有相应的视图来显示应用的数据。

总览页面

这是使用cats.index视图将要显示所有cats的主页。我们也会为第二个(通过猫的种类来过滤猫信息的)路由重用这个视图,因为两个路由都差不多一样(identical)。注意,Laravel想要你去用点标记(cats.index而不是cat/index)去和一个子目录的视图建立联系。

Route::get('cats', function(){
	$cats = Cat::all();
	return View::make('cats.index')->with('cats', $cats);
});
Route::get('cats/breeds/{name}', function($name){
	$breed = Breed::whereName($name)->with('cats')->first();
	return View::make('cats.index')->with('breed', $breed)->with('cats', $breed->cats);
});

在路由中比较新奇的(novelty)事是更先进的Eloquent查询。尽管我们已经知道在第一个路由中的all()方法是加载所有cats表项,这第二个路由用了稍微更复杂的查询。第一个方法,whereName,是一个动态方法,可以翻译成:WHERE name = $name SQL查询。这个with()方法加载cat相关模型,first()检索出第一个实例。

保存在cats/index.blade.php中的模板会是如下这样:

@extends('master')
@section('header')
@if(isset($breed))
{{link_to('/', 'Back to the overview')}}
@endif
<h2>
All @if(isset($breed)) {{$breed->name}} @endif Cats
<a href="{{url('cats/create')}}" class="btn btn-primary pull-right">
Add a new cat
</a>
</h2>
@stop
@section('content')
@foreach($cats as $cat)
<div class="cat">
<a href="{{url('cats/'.$cat->id)}}">
<strong> {{{$cat->name}}} </strong> - {{{$cat->breed->name}}}
</a>
</div>
@endforeach
@stop

在一个foreach的帮助下,视图迭代它从路由接收的cats的列表。因为我们将将用这个视图去显示index页面(/cats)和breed总览页面(/cats/breeds/breed),我们用@if指令在两个路由中有选择的显示更多的信息。

显示一个猫的页面

下一个路由被用来显示一个单独的cat。通过它的ID来查找一个猫,我们用Eloquent的find()方法:

Route::get('cats/{id}', function($id) {
	$cat = Cat::find($id);
	return View::make('cats.single')->with('cat', $cat);
});

因为这是一个非常普遍的模式,laravel给你提供了一种方法去自动的绑定一个model到一个路由上,因此,让你的代码更短和更有表现力。去将$cat变量绑定到Cat模型上,只用简单的添加如下声明在你的路由前:

Route::model('cat', 'Cat');

这允许你去简短你的路由,然后将一个Cat对象传给它:

Route::get('cats/{cat}', function(Cat $cat) {
	return View::make('cats.single')->with('cat', $cat);
});

视图, cats/single.blade.php,并不包含任何新的指令。它只是简单的显示cat名字,并且有去编辑和删除它的链接。在content section中,我们返回它的年龄和种类,如果种类被设置了。这会在下面的代码中展示:

@extends('master')
@section('header')
<a href="{{url('/')}}">Back to overview</a>
<h2>
{{{$cat->name}}}
</h2>
<a href="{{url('cats/'.$cat->id.'/edit')}}">
<span class="glyphicon glyphicon-edit"></span> Edit
</a>
<a href="{{url('cats/'.$cat->id.'/delete')}}">
<span class="glyphicon glyphicon-trash"></span> Delete
</a>
Last edited: {{$cat->updated_at}}
@stop
@section('content')
<p>Date of Birth: {{$cat->date_of_birth}} </p>
<p>
@if($cat->breed)
Breed:
{{link_to('cats/breeds/' . $cat->breed->name,
$cat->breed->name)}}
@endif
</p>
@stop

增加,编辑和删除猫

下面一系列的路由和视图将用来创建,编辑和删除一个猫的页面。因为对于这三个动作将要使用用同样的视图,我们传递了一个form必须用来完成动作的方法变量来区分:

Route::get('cats/create', function() {
	$cat = new Cat;
	return View::make('cats.edit')
	->with('cat', $cat)
	->with('method', 'post');
});

因为我们不能在一个模型存在前绑定它,我们仅仅传递一个新的和空的Cat模型实例给这个视图。对于编辑和删除的路由,然而,我们可以利用之前介绍的路由模型绑定,如下(as follows):

Route::get('cats/{cat}/edit', function(Cat $cat) {
	return View::make('cats.edit')
	->with('cat', $cat)
	->with('method', 'put');
});
Route::get('cats/{cat}/delete', function(Cat $cat) {
	return View::make('cats.edit')
	->with('cat', $cat)
	->with('method', 'delete');
});

下一个集合中的路由将会处理三种不同的action,然后重定向用户并使用一小段数据消息。这个消息可以通过Session::get(‘message’)使用Session对象将消息检索出来。任何通过应用接收的输入数据和你可以正常地通过 G E T 或 者 _GET或者 GET_POST访问的变量可以使用Input::get()方法检索出来。他们也使用了我们之前没见过的更多的Eloquent特性。这个POST /cats和PUT /cats/{cat}路由,分别使用Eloquent中的create()和update()方法,他们的参数都是Input::all()。这是唯一可能的因为我们之前在cat模型中指定具体的字段是fillable:

Route::post('cats', function(){
	$cat = Cat::create(Input::all());
	return Redirect::to('cats/' . $cat->id)
	->with('message', 'Successfully created page!');
});
Route::put('cats/{cat}', function(Cat $cat) {
	$cat->update(Input::all());
	return Redirect::to('cats/' . $cat->id)
	->with('message', 'Successfully updated page!');
});
Route::delete('cats/{cat}', function(Cat $cat) {
	$cat->delete();
	return Redirect::to('cats')
	->with('message', 'Successfully deleted page!');
});

在所有不同的路由里所用的视图中(The view used in all those differentroutes),views/cats/edit.blade.php,会稍微复杂一点,因为它需要一些额外的判断条件。然而它仍然比包含2个或者3个单独的文件去显示用来增加,编辑,删除资源的不同forms这种方法,要更容易维护。你将也遇到几个form帮助器,比如Form::label()或者Form::text(),从原理上来说这些帮助器会自动为你生成等价的HTML代码。

@extends('master')
@section('header')
<a href="{{('cats/'.$cat->id.'')}}">← Cancel </a>
<h2>
@if($method == 'post')
Add a new cat
@elseif($method == 'delete')
Delete {{$cat->name}}?
@else
Edit {{$cat->name}}
@endif
</h2>
@stop
@section('content')
{{Form::model($cat, array('method' => $method, 'url'=>
'cats/'.$cat->id))}}
@unless($method == 'delete')
<div class="form-group">
{{Form::label('Name')}}
{{Form::text('name')}}
</div>
<div class="form-group">
{{Form::label('Date of birth')}}
{{Form::text('date_of_birth')}}
</div>
<div class="form-group">
{{Form::label('Breed')}}
{{Form::select('breed_id', $breed_options)}}
</div>
{{Form::submit("Save", array("class"=>"btn btn-default"))}}
@else
{{Form::submit("Delete", array("class"=>"btn btn-default"))}}
@endif
{{Form::close()}}
@stop

在这个视图里,我们用了Laravel的一种极好(nifty)特性,它让我们绑定一个模型到一个form。这是通过Form::model()方法(这个方法希望一个模型的实例作为它的第一个参数)实现的。一旦一个模型被绑定到一个表上,任何要显示的字段会被自动地被生成成为模型的内容。这在当我们增加表验证(validation)的时候尤其有用。
这个Form::select()帮助器构建一个有不同选择的下拉菜单。它希望包含选项的列表通过一个多维(multidimensional )数组被传递。与其绑定这个数组到每一个路由,我们用view composers,这是laravel的另一个特性,它允许你每次绑定一个变量到一个具体的视图。去创建你的第一个view composer,简单地将下面的几行代码插到你的app/routes.php文件的底部:

View::composer('cats.edit', function($view)
{
	$breeds = Breed::all();
	if(count($breeds) > 0){
		$breed_options = array_combine($breeds->lists('id'),
		$breeds->lists('name'));
	} else {
		$breed_options = array(null, 'Unspecified');
	}
	$view->with('breed_options', $breed_options);
});

通过最后这一步,我们应用的第一个版本现在完成了!任何访客可以为他们猫创建一个页面,然后编辑和删除它。

总结

我们已经涉及到了很多在这章中。我们学习如何去定义路由,准备应用的模型,然后和它们交互。更多的,我们大体看了(have a glimpse at )很多Eloquent,Blade,和其他很多在Laravel中便利的帮助器去创建forms和输入字段:所有这些都在200行以内代码完成的!

在下一篇文章,我们将学习如何用Laravel的内置用户注册,验证和安全的特性,去提高我们目前的应用性能。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值