为 管理系统 做铺垫

This commit is contained in:
iVampireSP.com 2022-11-14 18:37:09 +08:00
parent 7c4f930fd5
commit 3c5b57b190
No known key found for this signature in database
GPG Key ID: 2F7B001CA27A8132
20 changed files with 1993 additions and 6 deletions

23
app/Models/Admin.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Admin extends Authenticatable
{
use HasFactory;
protected $table = 'admins';
protected $fillable = [
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
}

View File

@ -3,6 +3,7 @@
namespace App\Providers;
use App\Models\PersonalAccessToken;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Sanctum;
@ -28,6 +29,8 @@ public function boot()
{
//
Paginator::useBootstrapFive();
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
Http::macro('remote', function ($api_token, $url) {

View File

@ -39,6 +39,11 @@ public function boot()
->as('remote.')
->group(base_path('routes/remote.php'));
Route::middleware('web')
->prefix('admin')
->as('admin.')
->group(base_path('routes/admin.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});

View File

@ -83,7 +83,7 @@
|
*/
'locale' => 'en',
'locale' => 'zh_CN',
/*
|--------------------------------------------------------------------------

View File

@ -40,6 +40,12 @@
'driver' => 'session',
'provider' => 'users',
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
'module' => [
'driver' => 'token',
'provider' => 'modules',

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('admins', function (Blueprint $table) {
$table->id();
// email
$table->string('email')->unique();
// password
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('admins');
}
};

40
lang/zh_CN.json Normal file
View File

@ -0,0 +1,40 @@
{
"(and :count more error)": "(还有 :count 个错误)",
"(and :count more errors)": "(还有 :count 个错误)",
"All rights reserved.": "版权所有。",
"Forbidden": "访问被拒绝",
"Go to page :page": "前往第 :page 页",
"Hello!": "您好!",
"If you did not create an account, no further action is required.": "如果您未注册帐号,请忽略此邮件。",
"If you did not request a password reset, no further action is required.": "如果您未申请重设密码,请忽略此邮件。",
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "如果您单击「:actionText」按钮时遇到问题请复制下方链接到浏览器中访问",
"Login": "登录",
"Logout": "登出",
"Not Found": "页面不存在",
"of": "于",
"Page Expired": "页面会话已超时",
"Pagination Navigation": "分页导航",
"Please click the button below to verify your email address.": "请点击下面按钮验证您的 E-mail",
"Regards": "致敬",
"Register": "注册",
"Reset Password": "重置密码",
"Reset Password Notification": "重置密码通知",
"results": "结果",
"Server Error": "服务器错误",
"Service Unavailable": "服务不可用",
"Showing": "显示中",
"The :attribute must contain at least one letter.": ":Attribute 至少包含一个字母。",
"The :attribute must contain at least one number.": ":Attribute 至少包含一个数字。",
"The :attribute must contain at least one symbol.": ":Attribute 至少包含一个符号。",
"The :attribute must contain at least one uppercase and one lowercase letter.": ":Attribute 至少包含一个大写字母和一个小写字母。",
"The given :attribute has appeared in a data leak. Please choose a different :attribute.": "给定的 :attribute 出现在数据泄漏中。请选择不同的 :attribute。",
"The given data was invalid.": "给定的数据无效。",
"This password reset link will expire in :count minutes.": "这个重设密码链接将会在 :count 分钟后失效。",
"to": "至",
"Toggle navigation": "切换导航",
"Too Many Requests": "请求次数过多。",
"Unauthorized": "未授权",
"Verify Email Address": "验证 E-mail",
"Whoops!": "哎呀!",
"You are receiving this email because we received a password reset request for your account.": "您收到此电子邮件是因为我们收到了您帐户的密码重设请求。"
}

7
lang/zh_CN/auth.php Normal file
View File

@ -0,0 +1,7 @@
<?php
return [
'failed' => '用户名或密码错误。',
'password' => '密码错误',
'throttle' => '您尝试的登录次数过多,请 :seconds 秒后再试。',
];

View File

@ -0,0 +1,6 @@
<?php
return [
'next' => '下一页 &raquo;',
'previous' => '&laquo; 上一页',
];

9
lang/zh_CN/passwords.php Normal file
View File

@ -0,0 +1,9 @@
<?php
return [
'reset' => '密码重置成功!',
'sent' => '密码重置邮件已发送!',
'throttled' => '请稍候再试。',
'token' => '密码重置令牌无效。',
'user' => '找不到该邮箱对应的用户。',
];

204
lang/zh_CN/validation.php Normal file
View File

@ -0,0 +1,204 @@
<?php
return [
'accepted' => '您必须接受 :attribute。',
'accepted_if' => '当 :other 为 :value 时,必须接受 :attribute。',
'active_url' => ':Attribute 不是一个有效的网址。',
'after' => ':Attribute 必须要晚于 :date。',
'after_or_equal' => ':Attribute 必须要等于 :date 或更晚。',
'alpha' => ':Attribute 只能由字母组成。',
'alpha_dash' => ':Attribute 只能由字母、数字、短划线(-)和下划线(_)组成。',
'alpha_num' => ':Attribute 只能由字母和数字组成。',
'array' => ':Attribute 必须是一个数组。',
'before' => ':Attribute 必须要早于 :date。',
'before_or_equal' => ':Attribute 必须要等于 :date 或更早。',
'between' => [
'array' => ':Attribute 必须只有 :min - :max 个单元。',
'file' => ':Attribute 必须介于 :min - :max KB 之间。',
'numeric' => ':Attribute 必须介于 :min - :max 之间。',
'string' => ':Attribute 必须介于 :min - :max 个字符之间。',
],
'boolean' => ':Attribute 必须为布尔值。',
'confirmed' => ':Attribute 两次输入不一致。',
'current_password' => '密码错误。',
'date' => ':Attribute 不是一个有效的日期。',
'date_equals' => ':Attribute 必须要等于 :date。',
'date_format' => ':Attribute 的格式必须为 :format。',
'declined' => ':Attribute 必须是拒绝的。',
'declined_if' => '当 :other 为 :value 时字段 :attribute 必须是拒绝的。',
'different' => ':Attribute 和 :other 必须不同。',
'digits' => ':Attribute 必须是 :digits 位数字。',
'digits_between' => ':Attribute 必须是介于 :min 和 :max 位的数字。',
'dimensions' => ':Attribute 图片尺寸不正确。',
'distinct' => ':Attribute 已经存在。',
'doesnt_end_with' => ':Attribute 不能以以下之一结尾: :values。',
'doesnt_start_with' => ':Attribute 不能以下列之一开头: :values。',
'email' => ':Attribute 不是一个合法的邮箱。',
'ends_with' => ':Attribute 必须以 :values 为结尾。',
'enum' => ':Attribute 值不正确。',
'exists' => ':Attribute 不存在。',
'file' => ':Attribute 必须是文件。',
'filled' => ':Attribute 不能为空。',
'gt' => [
'array' => ':Attribute 必须多于 :value 个元素。',
'file' => ':Attribute 必须大于 :value KB。',
'numeric' => ':Attribute 必须大于 :value。',
'string' => ':Attribute 必须多于 :value 个字符。',
],
'gte' => [
'array' => ':Attribute 必须多于或等于 :value 个元素。',
'file' => ':Attribute 必须大于或等于 :value KB。',
'numeric' => ':Attribute 必须大于或等于 :value。',
'string' => ':Attribute 必须多于或等于 :value 个字符。',
],
'image' => ':Attribute 必须是图片。',
'in' => '已选的属性 :attribute 无效。',
'in_array' => ':Attribute 必须在 :other 中。',
'integer' => ':Attribute 必须是整数。',
'ip' => ':Attribute 必须是有效的 IP 地址。',
'ipv4' => ':Attribute 必须是有效的 IPv4 地址。',
'ipv6' => ':Attribute 必须是有效的 IPv6 地址。',
'json' => ':Attribute 必须是正确的 JSON 格式。',
'lt' => [
'array' => ':Attribute 必须少于 :value 个元素。',
'file' => ':Attribute 必须小于 :value KB。',
'numeric' => ':Attribute 必须小于 :value。',
'string' => ':Attribute 必须少于 :value 个字符。',
],
'lte' => [
'array' => ':Attribute 必须少于或等于 :value 个元素。',
'file' => ':Attribute 必须小于或等于 :value KB。',
'numeric' => ':Attribute 必须小于或等于 :value。',
'string' => ':Attribute 必须少于或等于 :value 个字符。',
],
'mac_address' => ':Attribute 必须是一个有效的 MAC 地址。',
'max' => [
'array' => ':Attribute 最多只有 :max 个单元。',
'file' => ':Attribute 不能大于 :max KB。',
'numeric' => ':Attribute 不能大于 :max。',
'string' => ':Attribute 不能大于 :max 个字符。',
],
'max_digits' => ':Attribute 不能超过 :max 位数。',
'mimes' => ':Attribute 必须是一个 :values 类型的文件。',
'mimetypes' => ':Attribute 必须是一个 :values 类型的文件。',
'min' => [
'array' => ':Attribute 至少有 :min 个单元。',
'file' => ':Attribute 大小不能小于 :min KB。',
'numeric' => ':Attribute 必须大于等于 :min。',
'string' => ':Attribute 至少为 :min 个字符。',
],
'min_digits' => ':Attribute 必须至少有 :min 位数。',
'multiple_of' => ':Attribute 必须是 :value 中的多个值。',
'not_in' => '已选的属性 :attribute 非法。',
'not_regex' => ':Attribute 的格式错误。',
'numeric' => ':Attribute 必须是一个数字。',
'password' => [
'letters' => ':Attribute 必须至少包含一个字母。',
'mixed' => ':Attribute 必须至少包含一个大写字母和一个小写字母。',
'numbers' => ':Attribute 必须至少包含一个数字。',
'symbols' => ':Attribute 必须至少包含一个符号。',
'uncompromised' => '给定的 :attribute 出现在数据泄漏中。请选择不同的 :attribute。',
],
'present' => ':Attribute 必须存在。',
'prohibited' => ':Attribute 字段被禁止。',
'prohibited_if' => '当 :other 为 :value 时,禁止 :attribute 字段。',
'prohibited_unless' => ':Attribute 字段被禁止,除非 :other 位于 :values 中。',
'prohibits' => ':Attribute 字段禁止出现 :other。',
'regex' => ':Attribute 格式不正确。',
'required' => ':Attribute 不能为空。',
'required_array_keys' => ':Attribute 至少包含指定的键::values.',
'required_if' => '当 :other 为 :value 时 :attribute 不能为空。',
'required_if_accepted' => '当 :other 存在时,:attribute 不能为空。',
'required_unless' => '当 :other 不为 :values 时 :attribute 不能为空。',
'required_with' => '当 :values 存在时 :attribute 不能为空。',
'required_with_all' => '当 :values 存在时 :attribute 不能为空。',
'required_without' => '当 :values 不存在时 :attribute 不能为空。',
'required_without_all' => '当 :values 都不存在时 :attribute 不能为空。',
'same' => ':Attribute 和 :other 必须相同。',
'size' => [
'array' => ':Attribute 必须为 :size 个单元。',
'file' => ':Attribute 大小必须为 :size KB。',
'numeric' => ':Attribute 大小必须为 :size。',
'string' => ':Attribute 必须是 :size 个字符。',
],
'starts_with' => ':Attribute 必须以 :values 为开头。',
'string' => ':Attribute 必须是一个字符串。',
'timezone' => ':Attribute 必须是一个合法的时区值。',
'unique' => ':Attribute 已经存在。',
'uploaded' => ':Attribute 上传失败。',
'url' => ':Attribute 格式不正确。',
'uuid' => ':Attribute 必须是有效的 UUID。',
'attributes' => [
'address' => '地址',
'age' => '年龄',
'amount' => '数额',
'area' => '区域',
'available' => '可用的',
'birthday' => '生日',
'body' => '身体',
'city' => '城市',
'content' => '内容',
'country' => '国家',
'created_at' => '创建于',
'creator' => '创建者',
'current_password' => '当前密码',
'date' => '日期',
'date_of_birth' => '出生日期',
'day' => '天',
'deleted_at' => '删除于',
'description' => '描述',
'district' => '地区',
'duration' => '期间',
'email' => '邮箱',
'excerpt' => '摘要',
'filter' => '过滤',
'first_name' => '名',
'gender' => '性别',
'group' => '组',
'hour' => '时',
'image' => '图像',
'last_name' => '姓',
'lesson' => '课程',
'line_address_1' => '线路地址 1',
'line_address_2' => '线路地址 2',
'message' => '信息',
'middle_name' => '中间名字',
'minute' => '分',
'mobile' => '手机',
'month' => '月',
'name' => '名称',
'national_code' => '国家代码',
'number' => '数字',
'password' => '密码',
'password_confirmation' => '确认密码',
'phone' => '电话',
'photo' => '照片',
'postal_code' => '邮政编码',
'price' => '价格',
'province' => '省',
'recaptcha_response_field' => '重复验证码响应字段',
'remember' => '记住',
'restored_at' => '恢复于',
'result_text_under_image' => '图像下的结果文本',
'role' => '角色',
'second' => '秒',
'sex' => '性别',
'short_text' => '短文本',
'size' => '大小',
'state' => '状态',
'street' => '街道',
'student' => '学生',
'subject' => '主题',
'teacher' => '教师',
'terms' => '条款',
'test_description' => '测试说明',
'test_locale' => '测试语言环境',
'test_name' => '测试名称',
'text' => '文本',
'time' => '时间',
'title' => '标题',
'updated_at' => '更新于',
'username' => '用户名',
'year' => '年',
],
];

1446
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,13 @@
"build": "vite build"
},
"devDependencies": {
"@popperjs/core": "^2.10.2",
"axios": "^1.1.2",
"bootstrap": "^5.1.3",
"laravel-vite-plugin": "^0.6.0",
"lodash": "^4.17.19",
"postcss": "^8.1.14",
"sass": "^1.32.11",
"vite": "^3.0.0"
}
}

View File

View File

@ -1,13 +1,15 @@
import _ from 'lodash';
import axios from 'axios';
window._ = _;
import 'bootstrap';
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
@ -26,7 +28,7 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: import.meta.env.VITE_PUSHER_APP_KEY,
// wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',

View File

@ -0,0 +1,7 @@
// Body
$body-bg: #f8fafc;
// Typography
$font-family-sans-serif: 'Nunito', sans-serif;
$font-size-base: 0.9rem;
$line-height-base: 1.6;

26
resources/sass/app.scss Normal file
View File

@ -0,0 +1,26 @@
// Fonts
@import url("https://fonts.bunny.net/css?family=Nunito");
// Variables
@import "variables";
// Bootstrap
@import "bootstrap/scss/bootstrap";
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 400 !important;
}
.pb-0 {
padding-bottom: 0 !important;
}
.mb-0 {
margin-bottom: 0 !important;
}

View File

@ -0,0 +1,73 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Login') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('login') }}">
@csrf
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,84 @@
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title')</title>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<!-- Scripts -->
@vite(['resources/sass/app.scss', 'resources/js/app.js'])
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ route('admin.index') }}">
{{ config('app.name') }}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.merchants.index') }}">商户</a>
</li>
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<!-- Authentication Links -->
@guest
@if (Route::has('login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::guard('admin')->user()->email }}
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('admin.logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('admin.logout') }}" method="POST"
class="d-none">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<main class="py-4">
<x-alert />
@yield('content')
</main>
</div>
</body>
</html>

View File

@ -1,10 +1,13 @@
import {defineConfig} from 'vite';
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
input: [
'resources/sass/app.scss',
'resources/js/app.js',
],
refresh: true,
}),
],