diff --git a/app/Http/Controllers/NotificationsController.php b/app/Http/Controllers/NotificationsController.php new file mode 100644 index 0000000..0b8e3d9 --- /dev/null +++ b/app/Http/Controllers/NotificationsController.php @@ -0,0 +1,13 @@ +notifications()->paginate(20); + \Auth::user()->markAsRead(); + return view('notifications.index', compact('notifications')); + } +} diff --git a/app/Models/Topic.php b/app/Models/Topic.php index 8be0669..a126e1e 100644 --- a/app/Models/Topic.php +++ b/app/Models/Topic.php @@ -7,7 +7,7 @@ class Topic extends Model protected $fillable = ['title', 'category_id', 'body', 'excerpt', 'slug']; - public function link($params = []) + public function link(...$params) { return route('topics.show', array_merge([$this->id, $this->slug], $params)); } diff --git a/app/Models/User.php b/app/Models/User.php index 3eb3ca7..67cd6b7 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,10 +4,13 @@ namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Facades\Auth; class User extends Authenticatable { - use Notifiable; + use Notifiable { + notify as protected laravelNotify; + } /** * The attributes that are mass assignable. @@ -46,4 +49,20 @@ class User extends Authenticatable { return $this->hasMany(Topic::class); } + + public function notify($instance) + { + if ($this->id == Auth::id()) { + return; + } + $this->increment('notification_count'); + $this->laravelNotify($instance); + } + + public function markAsRead() + { + $this->notification_count = 0; + $this->save(); + $this->unreadNotifications->markAsRead(); + } } diff --git a/app/Notifications/TopicReplied.php b/app/Notifications/TopicReplied.php new file mode 100644 index 0000000..75701f7 --- /dev/null +++ b/app/Notifications/TopicReplied.php @@ -0,0 +1,81 @@ +replay = $reply; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['database']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->line('The introduction to the notification.') + ->action('Notification Action', url('/')) + ->line('Thank you for using our application!'); + } + + public function toDataBase($notifiable) + { + $topic = $this->replay->topic; + + $link = $topic->link('#reply', $this->replay->id); + + return [ + 'reply_id' => $this->replay->id, + 'reply_content' => $this->replay->content, + 'user_id' => $this->replay->user->id, + 'user_name' => $this->replay->user->name, + 'user_avatar' => $this->replay->user->avatar, + 'topic_link' => $link, + 'topic_id' => $topic->id, + 'topic_title' => $topic->title + ]; + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } +} diff --git a/app/Observers/ReplyObserver.php b/app/Observers/ReplyObserver.php index aee8d0b..61c35f1 100644 --- a/app/Observers/ReplyObserver.php +++ b/app/Observers/ReplyObserver.php @@ -3,6 +3,7 @@ namespace App\Observers; use App\Models\Reply; +use App\Notifications\TopicReplied; // creating, created, updating, updated, saving, // saved, deleting, deleted, restoring, restored @@ -21,6 +22,10 @@ class ReplyObserver public function created(Reply $reply) { + $topic = $reply->topic; $reply->topic->increment('reply_count', 1); + if (!$reply->user->isAuthorOf($topic)) { + $topic->user->notify(new TopicReplied($reply)); + } } } \ No newline at end of file diff --git a/database/migrations/2018_01_27_165522_create_notifications_table.php b/database/migrations/2018_01_27_165522_create_notifications_table.php new file mode 100644 index 0000000..9797596 --- /dev/null +++ b/database/migrations/2018_01_27_165522_create_notifications_table.php @@ -0,0 +1,35 @@ +uuid('id')->primary(); + $table->string('type'); + $table->morphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('notifications'); + } +} diff --git a/database/migrations/2018_01_27_165914_add_notification_cout_to_users_table.php b/database/migrations/2018_01_27_165914_add_notification_cout_to_users_table.php new file mode 100644 index 0000000..e010b66 --- /dev/null +++ b/database/migrations/2018_01_27_165914_add_notification_cout_to_users_table.php @@ -0,0 +1,32 @@ +integer('notification_count')->unsigned()->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('notification_count'); + }); + } +} diff --git a/public/css/app.css b/public/css/app.css index e50247b..ec8d177 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -8975,5 +8975,15 @@ body { color: #b3b3b3; } -/* Topic Index Page */ +.notifications-badge { + margin-top: -1; +} + +.notifications-badge .badge-fade { + background-color: #EBE8E8; +} + +.notifications-badge .badge-hint { + background-color: #d15b47 !important; +} diff --git a/resources/assets/sass/app.scss b/resources/assets/sass/app.scss index b2f949a..2f593c8 100644 --- a/resources/assets/sass/app.scss +++ b/resources/assets/sass/app.scss @@ -174,4 +174,14 @@ body { } } -/* Topic Index Page */ \ No newline at end of file +.notifications-badge { + margin-top: -1; + + .badge-fade { + background-color: #EBE8E8; + } + + .badge-hint { + background-color: #d15b47 !important;; + } +} \ No newline at end of file diff --git a/resources/views/layouts/_header.blade.php b/resources/views/layouts/_header.blade.php index ddc3366..59bddb1 100644 --- a/resources/views/layouts/_header.blade.php +++ b/resources/views/layouts/_header.blade.php @@ -45,6 +45,15 @@ +
  • + + + {{Auth::user()->notification_count}} + + +
  • +
  • + diff --git a/resources/views/notifications/index.blade.php b/resources/views/notifications/index.blade.php new file mode 100644 index 0000000..d3242c9 --- /dev/null +++ b/resources/views/notifications/index.blade.php @@ -0,0 +1,34 @@ +@extends('layouts.app') + +@section('title','你的通知') +@section('content') +
    +
    +
    + +
    + +

    + 我的通知 +

    +
    + + @if ($notifications->count()) + +
    + @foreach ($notifications as $notification) + @include('notifications.types._' . snake_case(class_basename($notification->type))) + @endforeach + + {!! $notifications->render() !!} +
    + + @else +
    没有消息通知!
    + @endif + +
    +
    +
    +
    +@endsection \ No newline at end of file diff --git a/resources/views/notifications/types/_topic_replied.blade.php b/resources/views/notifications/types/_topic_replied.blade.php new file mode 100644 index 0000000..6b78683 --- /dev/null +++ b/resources/views/notifications/types/_topic_replied.blade.php @@ -0,0 +1,27 @@ +
    + +
    +
    + {{ $notification->data['user_name'] }} + 评论了 + {{ $notification->data['topic_title'] }} + + {{-- 回复删除按钮 --}} + + + {{ $notification->created_at->diffForHumans() }} + +
    +
    + {!! $notification->data['reply_content'] !!} +
    +
    +
    + +
    \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 42b9b53..3120c7b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -38,4 +38,5 @@ Route::resource('categories', 'CategoriesController', ['only' => ['show']]); Route::post('upload_image', 'TopicsController@uploadImage')->name('topics.upload_image'); -Route::resource('replies', 'RepliesController', ['only' => ['store', 'destroy']]); \ No newline at end of file +Route::resource('replies', 'RepliesController', ['only' => ['store', 'destroy']]); +Route::resource('notifications', 'NotificationsController', ['only' => ['index']])->middleware('auth'); \ No newline at end of file