Trong bài viết trước mình đã chia sẻ với mọi người cách kết hợp giữa Laravel với React, bằng cách tạo hai project riêng biệt. Thì trong bài viết hôm này mình cũng sử dụng project của bài viết trước cũng kết hợp Laravel + React y chang bài viết trước, bạn nào chưa xem hãy xem lại tại đây: USING REACT IN LARAVEL 8
+ Install Laravel 8
composer create-project laravel/laravel back-end-laravel8 php artisan ui vue --auth php artisan ui react --auth npm install npm run dev
Sau khi tải project thành công, Chúng ta cần thiếp lập database cho project của chúng ta , hãy mở file .env lên edit database như sau:
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel8 DB_USERNAME=root DB_PASSWORD=
Sau khi thiết lập xong, ta chạy lệnh migrate sau để laravel tạo một số table sẵn cho ta:
php artisan migrate
Okay, giờ hãy vào database của bạn sẽ thấy một số table như sau:
Okay, bạn hãy thử chạy câu lệnh sau để chạy project Laravel
php artisan server
Gõ http://127.0.0.1:8000 lên xem website hoạt động chưa nghe
Nếu chạy được là ok rồi nhé, ở bài chia sẻ này, mình có upload hình ảnh avatar cho user khi đăng ký, nên mình cần phải add column (avatar) đến table "users" trong database
Tạo lệnh migration thêm cột "avatar"
php artisan make:migration create_add_avatar_users --create=users
Sau khi chạy lệnh trên ta sẽ được file migration trong thư mục app/database/migrations như hình trên đây, hãy mời file vừa được tạo lên chỉnh sửa lại như sau:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateAddAvatarUsers extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function (Blueprint $table) { $table->string('avatar'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } }
Chỉnh xong, bạn chạy lệnh dưới đây để add cột "avatar" đến table "users"
php artisan migrate
Phần thiết lập database ok rồi, giờ ta cần thiết lập trong Controllers để xử lý (login,register)
Tạo file UsersController.php trong thư mục app\Http\Controllers\Api
php artisan make:controller Api\UsersController --resource
Mở file Api\UsersController.php
<?php namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use App\Http\Resources\UsersResource; use App\Models\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Carbon\Carbon; class UsersController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { return new UsersResource(User::all()); } public function onLogin(Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|string|email', 'password' => 'required|string', ]); if ($validator->fails()) { return response()->json(['error'=>"Login không thành công!"], 401); } $user = User::where("email",$request->email)->get(); if($user->count()>0){ return Response()->json(array("success"=>1,"data"=>$user[0])); } return response()->json(['error'=>"Login không thành công!"], 401); } public function onRegister(Request $request){ $validator = Validator::make($request->all(), [ 'name' => 'required', 'email' => 'required|email|unique:users', 'password' => 'required', 'confirm_password' => 'required|same:password', ]); if ($validator->fails()) { return response()->json(['error'=>$validator->errors()], 401); } $postArray = [ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), 'remember_token' => $request->token, 'created_at'=> Carbon::now('Asia/Ho_Chi_Minh'), 'updated_at'=>Carbon::now('Asia/Ho_Chi_Minh'), 'avatar'=> $request->file('UrlImage')->getClientOriginalName() ]; if ($request->hasFile('UrlImage')) { $image = $request->file('UrlImage'); $name = $image->getClientOriginalName(); $destinationPath = public_path('/upload/images'); $imagePath = $destinationPath . "/" . $name; $image->move($destinationPath, $name); } $user = User::create($postArray); return Response()->json(array("success"=> 1,"data"=>$postArray )); } }
- Trong đoạn code trên, function onRegister() dùng xử lý việc đăng ký của khách hàng
Đoạn code dưới đây ta cần kiểm tra thông tin request được gửi đến server xem có đúng yêu cầu không
$validator = Validator::make($request->all(), [ 'name' => 'required', 'email' => 'required|email|unique:users', 'password' => 'required', 'confirm_password' => 'required|same:password', ]);
+ name,password : bắt buộc phải có
+ email : không được trùng với email đã có trong database
+ password & confirm_password : phải giống nhau
Nếu request yêu cầu gửi đến không phù hợp thì ta sẻ trả về lỗi
if ($validator->fails()) { return response()->json(['error'=>$validator->errors()], 401); }
Nếu request yêu cầu đạt được thì ta sẽ cấu hình các thuộc tính để create một user mới đến database
$postArray = [ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), 'remember_token' => $request->token, 'created_at'=> Carbon::now('Asia/Ho_Chi_Minh'), 'updated_at'=>Carbon::now('Asia/Ho_Chi_Minh'), 'avatar'=> $request->file('UrlImage')->getClientOriginalName() ];
Trong phần đăng ký này, mình có thêm một ảnh avatar, nên ta cần phải upload hình ảnh đó đến thư mục "public/upload/images"
if ($request->hasFile('UrlImage')) { $image = $request->file('UrlImage'); $name = $image->getClientOriginalName(); $destinationPath = public_path('/upload/images'); $imagePath = $destinationPath . "/" . $name; $image->move($destinationPath, $name); }
Cuối cùng là ta sẽ create user bằng câu lệnh dưới đây, sau đó trả về thông tin cho front-end biết
$user = User::create($postArray); return Response()->json(array("success"=> 1,"data"=>$postArray ));
Với function onLogin() ta cũng xử lý yêu cầu tương tự như phần đăng ký
Kiểm tra request yêu cầu gửi đến server có đúng không
$validator = Validator::make($request->all(), [ 'email' => 'required|string|email', 'password' => 'required|string', ]);
Nếu request không đúng yêu cầu thì ta sẽ return về lỗi
if ($validator->fails()) { return response()->json(['error'=>"Login không thành công!"], 401); }
Kiểm tra thông tin đăng nhập có khớp với dữ liệu không
$user = User::where("email",$request->email)->get(); if($user->count()>0){ return Response()->json(array("success"=>1,"data"=>$user[0])); }
Mở file routes/api.php thiết lập URL sao
<?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use app\Http\Controller\Api\UsersController; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- | | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | is assigned the "api" middleware group. Enjoy building your API! | */ Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); /* Api Register */ Route::get('token', function (Request $request) { $token = $request->session()->token(); $token = csrf_token(); return Response()->json(array("token"=>$token)); }); Route::post('/users/login', [App\Http\Controllers\Api\UsersController::class, 'onLogin'])->name('user.login'); Route::post('/users', [App\Http\Controllers\Api\UsersController::class, 'onRegister'])->name('user.register');
Trong đoạn code trên ta thấy có một route get token , để có thể tạo token trong file api.php ta cần phải khai báo trong file app\Http\Kernel.php bạn nhìn code dưới chổ 'api'
protected $middlewareGroups = [ 'web' => [ // ], 'api' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ];
+ routes/web.php
<?php use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ /* Route::get('/', function () { return view('welcome'); }); */ Auth::routes(); Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home'); Route::get('{any}', function () { return view('index'); // or wherever your React app is bootstrapped. })->where('any', '.*');
Trong file routes/web.php bạn nhìn sẽ thấy đoạn code dưới đây, là bởi vì mình cài auth của laravel
Auth::routes(); Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Còn code dưới đây mình để nằm bên dưới đoạn code Auth::routes() để nói, khi nào route đứng trước không có, nó sẽ chạy vào route {any} này
Route::get('{any}', function () { return view('index'); //call resources/views/index.blade.php })->where('any', '.*');
File resources/views/index.blade.php các bạn có thể xem lại bài USING REACT IN LARAVEL 8 để biết cách cấu hình nhé
+ Ở đây mình nói thêm, nếu các bạn xử lý restful api mà bị lỗi Cors , thì các bạn cài đặt thư viện này đề fix nó nhé : https://github.com/fruitcake/laravel-cors
+ Phần xử lý Backend đã xong rồi, giờ chúng ta chỉ cần mở project front-end để viết layout xử lý thôi
* Cài đặt React xử lý Front-End
npx create-react-app front-end-react cd front-end-react npm start
Sau khi mọi thứ ok rồi, ta cần cài đặt bộ định tuyến route để chuyển tới lui các component trong React
npm install react-router-dom
Bạn có thể xem lại cách cấu hình router : URL ROUTER IN REACT
Tạo file theo đường dẫn src/components/Home.js
import React from 'react' const Home = () => { return ( <div className="styleHome"> <h1>Home</h1> </div> ) } export default Home
+ src/components/Login.js
import React,{useState,useEffect} from 'react' const Login=()=>{ const [dataForm,setDataForm] = useState({ "email":"", "password":"" }); const onLogin = () => { if(dataForm.email!="" && dataForm.password!=""){ const _formData = new FormData(); _formData.append("email",dataForm.email) _formData.append("password",dataForm.password) const requestOptions = { method: 'POST', body: _formData }; fetch('http://127.0.0.1:8000/api/users/login', requestOptions) .then(res => res.json()) .then(json =>{ console.log(json) }); } } return ( <div className="form-login"> <div className="box-login"> <div className="form-group-login"> <h3>Login Admin</h3> </div> <div className="form-group-login"> <label>Email</label> <input type="email" name="email" onChange={(e)=>setDataForm({...dataForm,"email":e.target.value})}/> </div> <div className="form-group-login"> <label>Password</label> <input type="password" name="password" onChange={(e)=>setDataForm({...dataForm,"password":e.target.value})}/> </div> <div className="form-group-login"> <button className="btn-login" onClick={()=>onLogin()}>Login</button> </div> </div> </div> ) } export default Login
Code trên ta cần cấu hình cấu trúc dữ liệu cho form, để lưu thông tin "email" & "password"
const [dataForm,setDataForm] = useState({ "email":"", "password":"" });
Dùng onChange() để cập nhật lại dữ liệu của cấu trúc dữ liệu form
<input type="email" name="email" onChange={(e)=>setDataForm({...dataForm,"email":e.target.value})}/> <input type="password" name="password" onChange={(e)=>setDataForm({...dataForm,"password":e.target.value})}/>
Xây dựng hàm onLogin() để xử lý thông tin đăng nhập, mình nghĩ đoạn code dưới đây quá quen thuộc với mọi người rồi, khai báo formdata() lưu dữ liệu để gửi đến server xử lý
const onLogin = () => { if(dataForm.email!="" && dataForm.password!=""){ const _formData = new FormData(); _formData.append("email",dataForm.email) _formData.append("password",dataForm.password) const requestOptions = { method: 'POST', body: _formData }; fetch('http://127.0.0.1:8000/api/users/login', requestOptions) .then(res => res.json()) .then(json =>{ console.log(json) }); } }
+ src/components/Register.js
import React, { useState, useEffect } from 'react' import {useHistory} from 'react-router-dom' import "../svg.css" const Register = () => { const history = useHistory(); /* register step */ const [phantram,setPhanTram] = useState(0); /* form data */ const [submit,setSubmit] = useState(false) const [dataForm, setDataForm] = useState({ "email": "", "name": "", "password": "", "confirm_password": "" }); /* check form register step */ const [checkForm, setCheckForm] = useState({ inputForm: false, uploadAvatar: false, btnRegister: false }) /* config avatar */ const [selectedFile, setSelectedFile] = useState() const [preview, setPreview] = useState() /* token */ const [token,setToken] = useState(""); useEffect(() => { setPhanTram(25); fetch('http://127.0.0.1:8000/api/token') .then(response => response.json()) .then(data => setToken(data.token)); return () => { } }, []); /* change avatar */ const onSelectFile = e => { if (!e.target.files || e.target.files.length === 0) { setSelectedFile(undefined) return } setSelectedFile(e.target.files[0]) const reader = new FileReader(); reader.onloadend = () => { setPreview(reader.result) } if (e.target.files[0]) { reader.readAsDataURL(e.target.files[0]); setPreview(reader.result) } else { setPreview(undefined) } } /* check form state */ const checkState = (value) => { if (value === 'inputForm') { if (dataForm.name.length > 2 && validateEmail(dataForm.email) && dataForm.password.length >= 8 && (dataForm.password == dataForm.confirm_password)) { setPhanTram(75) setCheckForm({ ...checkForm, inputForm: true }) } setSubmit(true) } if (value === "uploadAvatar") { if (selectedFile !== undefined) { setCheckForm({ ...checkForm, uploadAvatar: true }) setPhanTram(100) } } if (value === 'btnRegister') { const _formData = new FormData(); _formData.append("name", dataForm.name); _formData.append("email", dataForm.email); _formData.append("password", dataForm.password); _formData.append("confirm_password",dataForm.confirm_password) _formData.append("UrlImage", selectedFile); _formData.append("token", token); const requestOptions = { method: 'POST', /* headers: { 'Content-Type': 'application/json' }, */ body: _formData }; fetch('http://127.0.0.1:8000/api/users', requestOptions) .then(res => res.json()) .then(json =>{ if(json['success']>0){ alert("Bạn đã đăng ký thành công!"); history.push("/login") } else{ alert(JSON.stringify(json.error)) } }); } } const renderCheckValidationForm = () => { return ( <div className="errors" > {dataForm.name === "" && <span>Name bạn chưa nhập</span>} {dataForm.name.length <= 2 && <span>Name cần 3 ký tự</span>} {!validateEmail(dataForm.email) && <span>Email bạn không đúng</span>} {dataForm.password === "" && <span>Password bạn chưa nhập</span>} {dataForm.password.length < 8 && <span>Password cần 8 ký tự</span>} {dataForm.confirm_password === "" && <span>Confirm Password chưa nhập</span>} {dataForm.password !== dataForm.confirm_password && <span>Password không khớp</span>} </div> ) } const validateEmail = (email) => { const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(String(email).toLowerCase()); } const backForm = (value) => { if (value === 'inputForm') { setCheckForm({ ...checkForm, inputForm: false }) setPhanTram(25) } if (value === 'uploadAvatar') { setCheckForm({ ...checkForm, uploadAvatar: false }) setPhanTram(75) } } const renderFormRegister = <div className="form-register" > <div className="form-label"> <label>Name</label> <input type="text" name="name" placeholder="" onChange={(e) => setDataForm({ ...dataForm, "name": e.target.value })} value={dataForm.name} /> </div> <div className="form-label"> <label>Email</label> <input type="email" name="name" placeholder="" onChange={(e) => setDataForm({ ...dataForm, "email": e.target.value })} value={dataForm.email} /> </div> <div className="form-label"> <label>Password</label> <input type="password" name="name" placeholder="" onChange={(e) => setDataForm({ ...dataForm, "password": e.target.value })} value={dataForm.password} /> </div> <div className="form-label"> <label>Confirm Password</label> <input type="password" name="name" placeholder="" onChange={(e) => setDataForm({ ...dataForm, "confirm_password": e.target.value })} value={dataForm.confirm_password} /> </div> <div className="form-label"> <button className="btn-next" onClick={() => checkState("inputForm")}>Next</button> </div> <div className="form-label"> {submit && renderCheckValidationForm()} </div> </div> const renderUploadAvatar = <div className="form-upload"> <div className="upload-file"> <div className="box-avatar"> <label className="label-avatar">Avatar</label> {selectedFile && <img src={preview} alt="avatar" className="img-avatar" />} <input type="file" name="file" className="input-file" onChange={onSelectFile} /> </div> </div> <div className="upload-file"> <div className="box-event"> <button className="btn-next" onClick={() => backForm("inputForm")}>Back</button> <button className="btn-next" onClick={() => checkState("uploadAvatar")}>Next</button> </div> </div> </div> const renderEventRegister = <div className="form-event-register"> <div className="form-event"> <label>Vui lòng bấm xác nhận để hoàn thành đăng ký</label> <div className="box-event"> <button className="btn-next" onClick={() => backForm("uploadAvatar")}>Back</button> <button className="btn-next" onClick={() => checkState("btnRegister")}>Register</button> </div> { } </div> </div> return ( <div> <div className="boxState"> <div className="box"> <svg> <circle cx="70px" cy="70px" r="70px"></circle> <circle className="p50" className={phantram>0?'p'+phantram:'p25'} cx="70px" cy="70px" r="70px"></circle> </svg> <div className="number_precent"><span>{phantram}</span>%</div> </div> </div> { !checkForm.inputForm && renderFormRegister } { checkForm.inputForm && !checkForm.uploadAvatar && renderUploadAvatar } { checkForm.uploadAvatar && !checkForm.btnRegister && renderEventRegister } </div> ) } export default Register
Đoạn code trên hơi nhiều nhưng cũng đơn giản thôi mọi người!
Đâu tiên mình thiết lập các trạng thái như sau:
+ Thiếp lập trạng thái tiến trình % của các bước thao tác khi điền thông tin đăng ký form
const [phantram,setPhanTram] = useState(0);
+ Thiếp lập dữ liệu form nhập và button submit form
const [submit,setSubmit] = useState(false) const [dataForm, setDataForm] = useState({ "email": "", "name": "", "password": "", "confirm_password": "" });
+ Thiếp lập các bước của form, ở đây mình có 3 form, vì mình làm 3 bước để hoàn thành việc đăng ký
const [checkForm, setCheckForm] = useState({ inputForm: false, uploadAvatar: false, btnRegister: false })
+ Thiếp lập việc người dùng có chọn file hình, sau đó hiện thị hình ảnh avatar ra
const [selectedFile, setSelectedFile] = useState() const [preview, setPreview] = useState()
+ Thiết lập lấy token từ server
const [token,setToken] = useState("");
+ xử lý việc gọi API lấy token từ server, sau đó update state token lại
fetch('http://127.0.0.1:8000/api/token') .then(response => response.json()) .then(data => setToken(data.token));
+ Xây dựng function xử lý việc chọn hình ảnh
const onSelectFile = e => { if (!e.target.files || e.target.files.length === 0) { setSelectedFile(undefined) return } setSelectedFile(e.target.files[0]) const reader = new FileReader(); reader.onloadend = () => { setPreview(reader.result) } if (e.target.files[0]) { reader.readAsDataURL(e.target.files[0]); setPreview(reader.result) } else { setPreview(undefined) } }
+ Xây dựng hàm checkState() để xử lý từng bước của form
- Bắt sự kiện các trường field có nhập đầy đủ thông tin chưa
- Nếu hoàn thành bước nào, thì phải cập nhật lại trạng thái của form đó VD: setCheckForm({ ...checkForm, inputForm: true })
- Đồng thời cập nhật lại setPhanTram() cho từng bước
- Khai báo formdata() lưu thông tin dữ liệu gửi đến server xử lý việc đăng ký
const checkState = (value) => { if (value === 'inputForm') { if (dataForm.name.length > 2 && validateEmail(dataForm.email) && dataForm.password.length >= 8 && (dataForm.password == dataForm.confirm_password)) { setPhanTram(75) setCheckForm({ ...checkForm, inputForm: true }) } setSubmit(true) } if (value === "uploadAvatar") { if (selectedFile !== undefined) { setCheckForm({ ...checkForm, uploadAvatar: true }) setPhanTram(100) } } if (value === 'btnRegister') { const _formData = new FormData(); _formData.append("name", dataForm.name); _formData.append("email", dataForm.email); _formData.append("password", dataForm.password); _formData.append("confirm_password",dataForm.confirm_password) _formData.append("UrlImage", selectedFile); _formData.append("token", token); const requestOptions = { method: 'POST', /* headers: { 'Content-Type': 'application/json' }, */ body: _formData }; fetch('http://127.0.0.1:8000/api/users', requestOptions) .then(res => res.json()) .then(json =>{ if(json['success']>0){ alert("Bạn đã đăng ký thành công!"); history.push("/login") } else{ alert(JSON.stringify(json.error)) } }); } }
+ Xây dựng hàm validateEmail() kiểm tra giá trị email có đúng cú pháp "email" không
const validateEmail = (email) => { const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(String(email).toLowerCase()); }
+ Xây dựng hàm backForm() để xử lý việc, người dùng bấm nút lùi lại form trước
const backForm = (value) => { if (value === 'inputForm') { setCheckForm({ ...checkForm, inputForm: false }) setPhanTram(25) } if (value === 'uploadAvatar') { setCheckForm({ ...checkForm, uploadAvatar: false }) setPhanTram(75) } }
+ Bắt sự kiện để show form, chúng ta dựa vào thông tin được lưu trong checkForm mà ta xử lý việc hiển thị form
return ( <div> <div className="boxState"> <div className="box"> <svg> <circle cx="70px" cy="70px" r="70px"></circle> <circle className="p50" className={phantram>0?'p'+phantram:'p25'} cx="70px" cy="70px" r="70px"></circle> </svg> <div className="number_precent"><span>{phantram}</span>%</div> </div> </div> { !checkForm.inputForm && renderFormRegister } { checkForm.inputForm && !checkForm.uploadAvatar && renderUploadAvatar } { checkForm.uploadAvatar && !checkForm.btnRegister && renderEventRegister } </div> )
+ File src/App.css : chỉnh sửa giao diện
.menu-top{ padding:0 10px; background-color: #0082d0; display: flex; flex-wrap: wrap; justify-content: space-between; height: 40px; align-items: center; } .btnHome{ width: 60px; position: relative; } .btnHome a{ padding-right: 10px; box-sizing: border-box; text-decoration: none; font-size: 14px; background: #fff; position: absolute; top: -50%; left: 0; display: block; width: 40px; height: 40px; background-size: contain; transform: translateY(-50%); } .menu{ width: calc(100% - 60px); box-sizing: border-box; display: flex; flex-wrap: wrap; justify-content: flex-end; } .menu a{ color:#0082d0; background: #fff; box-sizing: border-box; text-decoration: none; font-size: 14px; border:1px solid #0082d0; font-weight: bold; margin: 0 1%; padding: 3px 10px; text-align: center; } /* boxState */ .boxState{ width: 100%; margin:30px auto; padding:10px; box-sizing: border-box; max-width: 80%; display: flex; justify-content: center; } .col-state{ height: 10px; width: 100%; border:1px solid #069B04; position: relative; border-radius: 5px; box-sizing: border-box; overflow: hidden; } .state-check{ position: absolute; top:0; left:0; height: 100%; width: 100%; background: #069B04; transition: .5s all ease-in-out; } .state-check p{ position: absolute; font-size: 10px; top: -2px; color: #fff; left:0; z-index: 10000; /* display: block; */ padding: 0; margin: 0; } /* REGISTER */ .form-register{ width:40%; margin: 0 auto; } .form-label{ width: 100%; margin: 5px 0px; display: flex; flex-wrap: wrap; vertical-align: middle; } .form-label label{ width:30%; display: flex; font-size: 13px; padding: 0 10px; box-sizing: border-box; background: #0082d0; color:#fff; align-items: center; border-top-left-radius:5px ; border-bottom-left-radius:5px ; } .form-label input{ width: calc(100% - 30%); padding:8px 10px; border:none; box-sizing: border-box; border:1px solid #000; border-left: none; outline: none; border-top-right-radius:5px ; border-bottom-right-radius:5px ; } .btn-next{ width: 100px; padding: 8px; box-sizing: border-box; border:none; background: #069B04; color:#fff; font-weight: bold; border-radius: 5px; } .errors{ width: 100%; } .errors span{ display: block; color:#000; font-size: 13px; padding: 5px; box-sizing: border-box; margin:2px 0px; background-color: #B3E5FC; border-radius: 5px; } /* Form upload avatar */ .form-upload{ width:40%; margin: 0 auto; } .upload-file{ margin:0 auto; } .label-avatar{ text-align: center; display: block; padding: 10px 0px; } .img-avatar{ width: 150px; display: block; margin: auto; } .box-event{ width: 100%; display: flex; justify-content: center; } .input-file{ display: block; width: 88px; color: #fff; margin: 5px auto; } .box-event button{ margin:0 5px; } /* succes register */ .form-event-register{ width:40%; margin: 0 auto; } .form-event{ display: flex; flex-direction: column; } .form-event label{ text-align: center; } .submit-register{ padding: 8px; box-sizing: border-box; border:none; background: #069B04; color:#fff; font-weight: bold; border-radius: 5px; } /* form-login */ .form-login{ width: 35%; margin:0 auto; } .box-login{ width:100%; } .form-group-login{ width:100%; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; margin:5px 0px; } .form-group-login h3{ color: #069B04; } .form-group-login label{ color:#0082d0; font-weight: bold; font-size: 13px; } .form-group-login input{ padding: 4px; box-sizing: border-box; border: 1px solid #069B04; outline: none; } .form-group-login button{ background-color: #069B04; padding:5px; border:none; color:#fff; } @media only screen and (max-width: 600px) { .form-register{ width: 90%; } }
+ src/svg.css : viết hình tròn % của tiến trình bằng css
.box { position: relative; height: 150px; width: 150px; } .box .number_precent { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); } .box svg { position: absolute; left: 0%; top: 0%; transform: translate(-50%,-50%); transform: rotate(-90deg); width: 150px; height: 150px; } .box svg circle { height: 150px; width: 150px; transform: translate(5px, 5px); fill: transparent; stroke: #007eff; stroke-width:10; stroke-linecap:round; stroke-dasharray: 440; stroke-dashoffset: 440; transition: all 1s; /*animation: animate 5s forwards;*/ } .box svg circle:nth-of-type(1) { stroke-dashoffset: 0; stroke: #dcd9d9; } .box svg circle.p25 { stroke-dashoffset: 330; } .box svg circle.p50 { stroke-dashoffset: 220 ; } .box svg circle.p75 { stroke-dashoffset: 110 ; } .box svg circle.p100 { stroke-dashoffset: 0 ; }
+ Mở file src/App.js lên khai báo thiết lập bộ định tuyến route để trỏ tới các components(Home,Login,Register)
import React from 'react'; import './App.css'; import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom' import Home from './components/Home' import Login from './components/Login' import Register from './components/Register' function App() { return ( <div className="container-fluid"> <Router> <div className="menu-top"> <div className="btnHome"> <Link to="/" style={{backgroundImage:`url(${process.env.PUBLIC_URL + '/bg-menu-top.png'})`}}></Link> </div> <div className="menu"> <Link to="/login">Login</Link> <Link to="/register">Register</Link> </div> </div> <Switch> <Route path="/" exact component={Home} /> <Route path="/login" component={Login} /> <Route path="/register" component={Register} /> </Switch> </Router> </div> ); } export default App;
Hoàn thành các bước trên là bạn đã thành công! Việc còn lại là phải build nó ra source để chạy
npx react-scripts build
Sau khi bạn chạy lệnh Build xong bạn sẽ được một folder "build"
- Bước 1: Copy file index.html tới project back-end-laravel8/resources/views/ đổi index.html thành index.blade.php
- Bước 2: Copy thư mục static và các file khác còn lại đến project back-end-laravel8/public
Chạy Project Laravel
php artisan serve
Vậy là xong nhé các bạn!