MichaelMano/Laramin

管理样式和脚本

0.3.1 2018-04-13 01:12 UTC

This package is auto-updated.

Last update: 2024-09-06 11:41:07 UTC


README

Laramin 是一个以材料设计为灵感的仪表板模板。我创建 Laramin 的目的是让您的 Laravel 项目快速启动,无需花费太多时间在设计管理面板和处理相关资产(如标签输入)的逻辑。

Laramin 不处理任何后端逻辑。

目录

安装

一旦您的 Laravel 项目运行起来,您可以使用 composer 安装 laramin:composer require michaelmano/laramin,然后将其添加到您的服务提供者中 config/app.php

MichaelMano\Laramin\LaraminServiceProvider::class,

然后使用以下命令发布资源:

php artisan vendor:publish --provider="MichaelMano\Laramin\LaraminServiceProvider"

强烈建议不要编辑 public/michaelmano/laramin 目录下的文件,因为 Laramin 仍在测试版,将会进行很多更新。

路由

由于 Laramin 是一个管理面板,我将从 php artisan make:auth 开始,然后编辑我的路由服务提供者 app/Providers/RouteServiceProvider.php,并在 mapWebRoutes 内添加以下内容。

protected function mapWebRoutes()
{
 Route::middleware('web')
   ->namespace($this->namespace)
   ->group(base_path('routes/web.php'));

 Route::middleware('web')
   ->namespace($this->namespace)
   ->group(base_path('routes/dashboard.php'));
}

然后在路由目录中创建一个名为 dashboard.php 的文件,这里是创建所有仪表板路由的地方。

<?php

Route::group([
    'middleware' => 'auth',
    'namespace' => 'Dashboard',
    'prefix' => 'dashboard',
    'as' => 'dashboard.'
], function () {
    Route::get('/', 'DashboardController@index')->name('index');
    Route::resource('/pages', 'PageController');
    Route::resource('/posts', 'PostController');
    Route::resource('/faqs', 'FaqController');
});

为了阻止用户注册,我将编辑 app/Http/Controllers/Auth/RegisterController.php 并在底部添加一个名为 forbidden 的新公共函数

public function forbidden()
{
 abort(403);
}

然后编辑 web/routes.php 文件并添加以下内容:

Route::get('register', 'Auth\RegisterController@forbidden');

但是,如果您的项目使用注册,您可以设置自己的中间件来处理仪表板。我还会更新

app/Http/Controllers/Auth/LoginController.php
app/Http/Controllers/Auth/RegisterController.php
app/Http/Controllers/Auth/ResetPasswordController.php

使用 protected $redirectTo = '/home'; 修改为 protected $redirectTo = '/dashboard';

并且

app/Http/Middleware/RedirectIfAuthenticated.php

return redirect('/home'); 修改为 return redirect('/dashboard');

视图

现在我将创建一个名为 dashboard 的视图文件夹 resources/views/dashboard 并创建一个 index.blade.php 文件,并在其中放置以下代码。

@extends('laramin::layouts.standard')
@push('styles')
 You can push any overrides or additional styles here.
@endpush
@section('standard-content')
 Content here
@endsection
@push('scripts')
 You can push any javascript here.
@endpush

这是用于 laramin 的 blade 布局。

登录表单

login

现在覆盖 resources/views/auth/login.blade.php 下的登录页面,使用以下内容。

@extends('laramin::layouts.login')

@section('login-form')
 <form class="Form Box Box--padded animated zoomIn"method="POST" action="{{ route('login') }}">
  {{ csrf_field() }}
  <fieldset class="Form__fieldset">
   <label class="Form__label" for="email">Email address</label>
   <input class="Form__input Form__input--email" name="email" id="email" type="email" placeholder="jane@doe.me" required autofocus>
  </fieldset>
  <fieldset class="Form__fieldset">
   <label class="Form__label" for="password">Password</label>
   <input class="Form__input Form__input--password" name="password" id="password" type="password" required>
  </fieldset>
  <fieldset class="Form__fieldset">
   <button class="Button" type="submit">Login</button>
  </fieldset>
  <fieldset class="Form__fieldset">
   <a class="Button Button--text-link" href="{{ route('password.request') }}">Forgot password?</a>
  </fieldset>
 </form>
@endsection

控制器

生成一个可以用于处理仪表板索引的控制器 php artisan make:controller Dashboard/DashboardController 以及可能需要的其他控制器,例如如果项目有动态页面,可以使用 php artisan make:controller Dashboard/PageController -r 通过传递 -r 参数为资源,例如 CRUD。

配置

配置文件位于项目下的 config/laramin.php

菜单

您可以通过编辑配置来设置菜单,此布局使用 font-awesome,用户头像将是一个 font awesome 用户图标,除非您的用户对象有指向图像 URL 的 $user->avatar。

'sidebar_links' => [
 [
  'url' => '/dashboard',
  'name' => 'Dashboard',
  'icon' => 'fa-window-maximize',
 ],
 [
  'url' => '/dashboard/pages',
  'name' => 'Pages',
  'icon' => 'fa-file-text',
 ],
 [
  'url' => '/dashboard/posts',
  'name' => 'Posts',
  'icon' => 'fa-newspaper-o',
 ],
 [
  'url' => '/dashboard/faqs',
  'name' => 'FAQ\'s',
  'icon' => 'fa-question',
 ],
],

项目管理器

项目管理器用于创建一个帮助表单,客户可以使用它发送电子邮件或获取他们的联系信息,如果您删除值,则帮助按钮将不会显示。

project-manager

'project_manager' => [
 'name' => 'John Smith',
 'email' => 'jsmith@example.com',
 'phone' => '555 55 555',
 'contact_finalised_message' => 'Thank you for contacting us, we will get back to you as soon as possible.',
],

元素

网格系统

Laramin 使用 Buzuki,这是一个移动优先、响应式 BEM 风格 flexbox CSS 网格系统。

水槽

如果项目设置为本地环境,laramin 有一个路由可以查看您可以在系统中使用的所有元素,同时显示代码。您可以从 http://your-domain.tld/laramin 访问它。

框元素只是一个具有阴影的元素,您还可以添加一个修饰类 Box--padded,这将向框添加填充。

砖墙

laramin 中的砖墙使用 CSS 网格,不使用任何 JavaScript。下面的代码使用带有 Cards 的 Masonry 元素。

masonry

<div class="Masonry__panel">
 <div class="Box Card">
  <header class="Card__header">
   <h5 class="Heading util-breakaway-bottom-2"><a href="{{ route('dashboard.posts.edit', $post) }}">{{ $post->title }}</a></h5>
   <small class="Heading__meta"><strong>Slug: </strong>{{ $post->slug }}</small>
  </header>
  <div class="Card__content">
   {{ $post->body }}
  </div>
  <footer class="Card__footer Row Row--valign-center">
   <div class="Cell Cell--12/12@xs Cell--4/12@xl">
    <a href="{{ route('dashboard.posts.edit', $post) }}" class="Button Button--round"><i class="fa fa-pencil"></i></a>
    @include('laramin::components.confirm-delete', [
     'value' => $post->title,
     'url' => route('dashboard.posts.destroy', $post),
     'remove' => '.Masonry__panel'
    ])
   </div>
   <div class="Cell Cell--12/12@xs Cell--8/12@xl Cell--align-right@xl">
    @foreach($post->tags as $tag)
     <div class="Tag">
      <div class="Tag__name">{{ $tag->name }}</div>
     </div>
    @endforeach
   </div>
  </footer>
 </div>
</div>

标签

tags

标签可以指定如下。

<div class="Tag">
 <div class="Tag__name">Tag with info</div>
 <div class="Tag__info">v2</div>
</div>
<div class="Tag">
 <div class="Tag__name">Tag without info</div>
</div>

<div class="Tag">
 <div class="Tag__name">Tag with delete</div>
 <div class="Tag__remove"><i class="fa fa-times"></i></div>
</div>

卡片

cards

卡片组件有 3 个元素,标题、内容和页脚。下面我在标题中放置了帖子的标题,以 slug 作为标题元数据,然后是帖子内容作为 Card__content,在页脚中我渲染了 删除项目 laravel 组件和帖子的 标签

<div class="Box Card">
    <header class="Card__header">
        <h5 class="Heading util-breakaway-bottom-2"><a href="{{ route('dashboard.posts.edit', $post) }}">{{ $post->title }}</a></h5>
        <small class="Heading__meta"><strong>Slug: </strong>{{ $post->slug }}</small>
    </header>
    <div class="Card__content">
        {{ $post->body }}
    </div>
    <footer class="Card__footer Row Row--valign-center">
        <div class="Cell Cell--12/12@xs Cell--4/12@xl">
            <a href="{{ route('dashboard.posts.edit', $post) }}" class="Button Button--round"><i class="fa fa-pencil"></i></a>
            @include('laramin::components.confirm-delete', [
                'value' => $post->title,
                'url' => route('dashboard.posts.destroy', $post),
                'remove' => '.Masonry__panel'
            ])
        </div>
        <div class="Cell Cell--12/12@xs Cell--8/12@xl Cell--align-right@xl">
            @foreach($post->tags as $tag)
                <div class="Tag">
                    <div class="Tag__name">{{ $tag->name }}</div>
                </div>
            @endforeach
        </div>
    </footer>
</div>

表单

forms

<form enctype="multipart/form-data" class="Form" method="POST" action="{{ route('login') }}">
 <fieldset class="Form__fieldset">
  <label class="Form__label" for="fullName">Text</label>
  <input class="Form__input Form__input--text" name="fullName" id="fullName" type="text" required>
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="phone">Phone</label>
  <input class="Form__input Form__input--tel" name="phone" id="phone" type="tel" required>
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="comment">Textarea</label>
  <textarea class="Form__input Form__input--textarea"></textarea>
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="date">Date</label>
  <input class="Form__input Form__input--date" type="date">
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="checkbox">Checkbox
   <input class="Form__input Form__input--checkbox" name="checkbox" id="checkbox" type="checkbox">
  </label>
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="radio">Radio
   <input class="Form__input Form__input--radio" name="radio" id="radio" type="radio">
  </label>
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="email">Email</label>
  <input class="Form__input Form__input--email" name="email" id="email" type="email">
 </fieldset>

 <fieldset class="Form__fieldset">
  <input type="file" name="file[]" id="file" class="Form__input Form__input--file" data-multiple-caption="{count} files selected" multiple />
  <label for="file"><i class="fa fa-upload"></i> <span>Choose a file&hellip;</span></label>
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="month">Month</label>
  <input class="Form__input Form__input--month" name="month" id="month" type="month">
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="number">Number</label>
  <input class="Form__input Form__input--number" name="number" id="number" type="number">
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="password">Password</label>
  <input class="Form__input Form__input--password" name="password" id="password" type="password">
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="range">Range</label>
  <input class="Form__input Form__input--range" name="range" id="range" type="range">
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="search">Search</label>
  <input class="Form__input Form__input--search" name="search" id="search" type="search">
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="time">Time</label>
  <input class="Form__input Form__input--time" name="time" id="time" type="time">
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="url">Url</label>
  <input class="Form__input Form__input--url" name="url" id="url" type="url">
 </fieldset>

 <fieldset class="Form__fieldset">
  <label class="Form__label" for="week">Week</label>
  <input class="Form__input Form__input--week" name="week" id="week" type="week">
 </fieldset>

 <fieldset class="Form__fieldset">
  <button class="Button" type="submit">Submit</button>
 </fieldset>
</form>

实用程序助手

边距

util-breakaway-${top/right/bottom/left}-${0/1/2/3/4/5/6/7/8}-${5} 可以这样使用 util-breakaway-bottom-0 从底部移除边距或 util-breakaway-top-1-5 向顶部添加 1.5rem 边距。

动画

Laramin 使用 animate.css 库 Animate.css

Vue 组件

模态框

modals

<button class="Button" @click="showModal('modal')">Show Modal</button>
<laramin-modal ref="modal" @close="hideModal">
 <template slot="title">Modal</template>
 <template slot="body">
  <p>Modal Body Content</p>
 </template>
 <p slot="footer">Footer Content</p>
</laramin-modal>

按钮点击将触发一个名为 showModal() 的函数,该函数接受一个参数,即模态框的引用,该引用也必须是唯一的 ref="modal",并且模态框可以传递一个动画属性 <laramin-modal ref="name" @close="hideModal" animation-in="flipInX" animation-out="flipOutX"> 默认动画为 bounceInLeftbounceOutRight

图像裁剪器

<laramin-crop image="http://via.placeholder.com/1920x800" label="file button text (default is choose an image)" :min-width="1920" :min-height="800" name="image"></laramin-crop>

名称和图像是必需的,名称将设置文件输入的名称,并在后端应用程序中创建一个隐藏的输入 ${name}_dimentions,您可以使用它来裁剪上传的图像。

min-height 和 min-width 属性用于设置图片的宽高比,如果用户尝试选择小于这些尺寸的图片,将会出现一个错误信息并重置文件输入,同时确保裁剪器的宽高比与设置的 width / height 相同。

您传递的图片将是默认图片,用户必须使用上传按钮选择一张图片后,裁剪器才会启用。

以下是我用来管理媒体的一个示例,它还使用了 Intervention Image 包。

<?php

namespace App\Traits;

use Image;
use Carbon\Carbon;
use Illuminate\Support\Facades\Storage;

trait MediaManager
{
    public function uploadImage($data)
    {
        $dimentions = json_decode($data['dimentions'], true);
        $width = floor($dimentions['width']);
        $height = floor($dimentions['height']);
        $x = floor($dimentions['x']);
        $y = floor($dimentions['y']);
        $size = $data['size'] ?? '1920, 800';

        $image_name = 'images/'.implode('-', [$this->id, Carbon::now()->timestamp, str_random(10)]).'.'.$data['image']->getClientOriginalExtension();

        $img = Image::make(file_get_contents($data['image']));
        $img->crop($width, $height, $x, $y);
        $img->resize($size);
        $img->stream();

        Storage::put('public/'.$image_name, $img);

        return $image_name;
    }

    public function deleteImage($location)
    {
        if (!empty($location) && Storage::exists('public/'.$location)) {
            return Storage::delete('public/'.$location);
        }
    }
}

现在您只需像这样在模型中使用这个属性

<?php

namespace App;

use App\Traits\MediaManager;
use Illuminate\Database\Eloquent\Model;

class Page extends Model
{
    use MediaManager;
}

在控制器中,您只需检查更新是否包含图片字段。

<?php

namespace App\Http\Controllers\Dashboard;

use App\Page;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class PageController extends Controller
{
    public function update(Page $page, Request $request)
    {
        if ($request->has(['image', 'image_dimentions'])) {
            $data = [
                'image' => $request->file('image'),
                'dimentions' => $request->get('image_dimentions')
            ];
            $page->deleteImage($page->feature_image);
            $page->feature_image = $page->uploadImage($data);
            $page->save();
        }
        $page->update($request->only('title', 'body'));

        return back()->withMessage('The page has been updated.');
    }
}

闪存信息

闪存信息可以通过两种方式传递,一种是在会话中设置一个名为 message(或 laravel 传递到表单字段的错误)的变量。第二种方式是如果您使用 JavaScript,则可以像这样将消息推送到 laramin 对象 laramin.messages.push({type: 'error', 'message'});,其中有两种类型。 errorsuccess

加载遮罩

加载遮罩可以像上面的闪存信息一样触发。 laramin.loading = true; 但是您必须在加载完成后禁用此功能 laramin.loading = false;,除非您正在等待页面刷新。以下是使用 Promise 设置可排序项的示例。

let promises = [];
laramin.loading = true;
Q.each(sortableInput, (sortableInput, index) => {
  let promise = axios.post(sortableInput.form.action, {
    _method: "PATCH",
    order: index + 1
  });
  promises.push(promise);
});
Promise.all(promises).then(() => {
  laramin.loading = false;
});

因此,我设置了窗口为加载状态,创建了一个 Promise,然后在它们全部完成时将加载状态设置为 false。

标签

tabs 标签组件使用起来非常简单。

<laramin-tabs>
 <laramin-tab name="Tab 1">
  <p>Content for tab 1</p>
 </laramin-tab>
 <laramin-tab name="Tab 2">
  <p>Content for tab 2</p>
 </laramin-tab>
</laramin-tabs>

这将创建一个标签组件,同时允许您通过使用名称作为哈希值链接到页面上的特定标签,例如 http://site-url.com/page-url#tab-2,这将打开标签 2 而不是 1。

标签输入

<laramin-tags-input
 name="tags[]"
 :autocomplete="['Suggestion 1',' Suggestion 2', 'Suggestion 3']"
 :tags="['tag1', 'tag2', 'tag3']">
</laramin-tags-input>`

这将创建一个输入并添加自动完成建议和默认标签,以便预先填充。name 属性还会创建具有标签输入中值的隐藏输入。例如,tag1、tag2、tag3,您可以这样使用。

<?php

namespace App\Http\Controllers\Dashboard;

use App\Post;
use App\Tag;
use App\Http\Controllers\Controller;

class PostController extends Controller
{
    public function update(Post $post)
    {
        $tags = collect(request('tags'))->map(function ($tag) {
            return Tag::firstOrCreate(['name' => ucfirst($tag)])->id;
        });
        $post->tags()->sync($tags);
        $post->update(request()->except('_token', '_method', 'tags'));

        return back()->withMessage('The post has been updated');
    }
}

工具提示

工具提示是自解释的。 <laramin-tooltip tooltip="测试工具提示">这将显示测试工具提示悬停时显示的内容。</laramin-tooltip>

Laravel 组件

删除项

@include('laramin::components.confirm-delete', [
 'value' => $page->title,
 'url' => route('dashboard.pages.destroy', $page),
 'delete_text' => 'Delete Page',
 'remove' => '.Masonry__panel'
])

这将创建一个按钮,文本为 删除页面。如果您未传递 delete_text,则它将仅显示一个垃圾桶,当点击时会显示一个包含输入的模态窗口,您必须在此处输入值才能删除它,例如页面标题。删除操作将通过 AJAX 完成并删除第一个具有指定类的父元素。

纯 JavaScript

可排序项

sortable

要使用可排序功能,您需要在模型中有一个名为 order 的字段,然后在渲染时像这样操作

<div class="Row js-sortable">
 @foreach($faqs as $faq)
  <div class="Cell Cell--12/12@xs js-sortable-item">
   <div class="Box Card">
    <header class="Card__header">
     <h6 class="Heading"><a href="{{ route('dashboard.faqs.edit', $faq) }}">{{ $faq->title }}</a></h6>
    </header>
    <div class="Card__content">
     {{ $faq->body }}
    </div>
    <footer class="Card__footer Row Row--valign-center">
     <div class="Cell Cell--align-left Cell--6/12@xs">
      <button class="Button Button--round js-sortable-tile"><i class="fa fa-arrows"></i></button>
      <form action="{{ route('dashboard.faqs.update', $faq) }}" method="POST">
       <input class="js-sortable-input" type="hidden" value="{{ $faq->order }}">
      </form>
     </div>
     <div class="Cell Cell--align-right Cell--6/12@xs">
      <a href="{{ route('dashboard.faqs.edit', $faq) }}" class="Button Button--round"><i class="fa fa-pencil"></i></a>
      @include('laramin::components.confirm-delete', [
       'value' => $faq->title,
       'url' => route('dashboard.faqs.destroy', $faq),
       'remove' => '.js-sortable-item'
      ])
     </div>
    </footer>
   </div>
  </div>
 @endforeach
</div>

这将渲染每个项目,每个项目都有一个隐藏的表单,它将更改 order 输入的值并将其发送到您指定的路由。只有通过可排序按钮 <button class="Button Button--round js-sortable-tile"><i class="fa fa-arrows"></i></button> 拖动时,项目才会更改顺序。