优化表单前端验证

This commit is contained in:
pushuo 2022-08-17 17:48:06 +08:00
parent 5b503d9038
commit 2655bb8895
20 changed files with 195 additions and 45 deletions

View File

@ -11,6 +11,9 @@ trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{js,scss}]
indent_size = 2
[*.{yml,yaml}]
indent_size = 2

View File

@ -11,13 +11,15 @@ class InputLocale extends Component
public string $title;
public string $width;
public $value;
public bool $required;
public function __construct(string $name, string $title, $value, ?string $width = '400')
public function __construct(string $name, string $title, $value, ?string $width = '400', ?bool $required = false)
{
$this->name = $name;
$this->title = $title;
$this->value = $value;
$this->width = $width;
$this->value = $value;
$this->required = $required;
}
public function render()

View File

@ -17012,3 +17012,8 @@ textarea.form-control-lg {
.btn-link:focus {
box-shadow: none;
}
.was-validated .form-control:valid, .form-control.is-valid {
border-color: #e2e2e2;
background-image: none;
}

View File

@ -2066,6 +2066,8 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./common */ "./resources/beike/admin/js/common.js");
/* harmony import */ var _autocomplete__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./autocomplete */ "./resources/beike/admin/js/autocomplete.js");
/* harmony import */ var _autocomplete__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_autocomplete__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _bootstrap_validation__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./bootstrap-validation */ "./resources/beike/admin/js/bootstrap-validation.js");
/* harmony import */ var _bootstrap_validation__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_bootstrap_validation__WEBPACK_IMPORTED_MODULE_3__);
var _document$querySelect;
@ -2073,6 +2075,7 @@ window.$http = _js_http__WEBPACK_IMPORTED_MODULE_0__["default"];
window.bk = _common__WEBPACK_IMPORTED_MODULE_1__["default"];
var base = document.querySelector('base').href;
var asset = document.querySelector('meta[name="asset"]').content;
var editor_language = ((_document$querySelect = document.querySelector('meta[name="editor_language"]')) === null || _document$querySelect === void 0 ? void 0 : _document$querySelect.content) || 'zh_cn';
@ -2297,6 +2300,30 @@ $(function () {
/***/ }),
/***/ "./resources/beike/admin/js/bootstrap-validation.js":
/*!**********************************************************!*\
!*** ./resources/beike/admin/js/bootstrap-validation.js ***!
\**********************************************************/
/***/ (() => {
// Example starter JavaScript for disabling form submissions if there are invalid fields
$(function () {
var forms = document.querySelectorAll(".needs-validation"); // Loop over them and prevent submission
Array.prototype.slice.call(forms).forEach(function (form) {
form.addEventListener("submit", function (event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add("was-validated");
}, false);
});
});
/***/ }),
/***/ "./resources/beike/admin/js/common.js":
/*!********************************************!*\
!*** ./resources/beike/admin/js/common.js ***!

View File

@ -17042,3 +17042,8 @@ textarea.form-control-lg {
.breadcrumb a {
color: #212529;
}
.was-validated .form-control:valid, .form-control.is-valid {
border-color: #ced4da;
background-image: none;
}

View File

@ -2052,6 +2052,30 @@ module.exports = {
};
/***/ }),
/***/ "./resources/beike/shop/default/js/bootstrap-validation.js":
/*!*****************************************************************!*\
!*** ./resources/beike/shop/default/js/bootstrap-validation.js ***!
\*****************************************************************/
/***/ (() => {
// Example starter JavaScript for disabling form submissions if there are invalid fields
$(function () {
var forms = document.querySelectorAll(".needs-validation"); // Loop over them and prevent submission
Array.prototype.slice.call(forms).forEach(function (form) {
form.addEventListener("submit", function (event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add("was-validated");
}, false);
});
});
/***/ }),
/***/ "./resources/beike/shop/default/js/common.js":
@ -2683,6 +2707,8 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _product__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_product__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _header__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./header */ "./resources/beike/shop/default/js/header.js");
/* harmony import */ var _header__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_header__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _bootstrap_validation__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./bootstrap-validation */ "./resources/beike/shop/default/js/bootstrap-validation.js");
/* harmony import */ var _bootstrap_validation__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_bootstrap_validation__WEBPACK_IMPORTED_MODULE_4__);
window.bk = _common__WEBPACK_IMPORTED_MODULE_1__["default"];
@ -2691,6 +2717,7 @@ var token = document.querySelector('meta[name="csrf-token"]').content;
var base = document.querySelector('base').href;
$(document).ready(function ($) {
$(document).on('click', '.offcanvas-products-delete', function () {
var _this = this;

View File

@ -69,4 +69,9 @@ $alert-padding-y: 0.5rem;
&:focus {
box-shadow: none;
}
}
.was-validated .form-control:valid, .form-control.is-valid {
border-color: $input-border-color;
background-image: none;
}

View File

@ -3,6 +3,7 @@ window.$http = http;
import common from "./common";
window.bk = common;
import "./autocomplete";
import "./bootstrap-validation";
const base = document.querySelector('base').href;
const asset = document.querySelector('meta[name="asset"]').content;

View File

@ -0,0 +1,20 @@
// Example starter JavaScript for disabling form submissions if there are invalid fields
$(function () {
var forms = document.querySelectorAll(".needs-validation");
// Loop over them and prevent submission
Array.prototype.slice.call(forms).forEach(function (form) {
form.addEventListener(
"submit",
function (event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add("was-validated");
},
false
);
});
});

View File

@ -2,7 +2,10 @@
@foreach (locales() as $index => $locale)
<div class="d-flex wp-{{ $width }}">
<span class="input-group-text wp-100 px-1" id="basic-addon1">{{ $locale['name'] }}</span>
<input type="text" name="{{ $formatName($locale['code']) }}" value="{{ $formatValue($locale['code']) }}" class="form-control short" placeholder="{{ $locale['name'] }}">
<input type="text" name="{{ $formatName($locale['code']) }}" value="{{ $formatValue($locale['code']) }}"
class="form-control short" placeholder="{{ $locale['name'] }}" @if ($required) required @endif>
<span class="invalid-feedback w-auto"
style="white-space:nowrap;">{{ __('common.error_required', ['name' => $title]) }}</span>
</div>
@if ($attributes->has('required'))
@error($errorKey($locale['code']))

View File

@ -1,7 +1,13 @@
<x-admin::form.row :title="$title" :required="$required">
<input type="text" name="{{ $name }}" class="form-control wp-{{ $width }} {{ $error ? 'is-invalid' : '' }}" value="{{ $value }}" placeholder="{{ $title }}">
@if ($error)
<span class="invalid-feedback" role="alert">{{ $error }}</span>
@endif
<input type="text" name="{{ $name }}"
class="form-control wp-{{ $width }} {{ $error ? 'is-invalid' : '' }}" value="{{ $value }}"
placeholder="{{ $title }}" @if ($required) required @endif>
<span class="invalid-feedback" role="alert">
@if ($error)
{{ $error }}
@else
{{ __('common.error_required', ['name' => $title]) }}
@endif
</span>
{{ $slot }}
</x-admin::form.row>

View File

@ -9,7 +9,7 @@
@section('content')
<div id="plugins-app-form" class="card h-min-600">
<div class="card-body">
<form action="{{ $page->id ? admin_route('pages.update', [$page->id]) : admin_route('pages.store') }}" method="POST">
<form novalidate class="needs-validation" action="{{ $page->id ? admin_route('pages.update', [$page->id]) : admin_route('pages.store') }}" method="POST">
@csrf
@method($page->id ? 'PUT' : 'POST')
@ -30,12 +30,13 @@
error="{{ $error_title }}"
name="descriptions[{{ $language['code'] }}][title]"
title="信息标题"
:required="true"
value="{{ old('title', $descriptions[$language['code']]['title'] ?? '') }}"
/>
<x-admin::form.row title="内容">
<div class="w-max-1000">
<textarea name="descriptions[{{ $language['code'] }}][content]" data-tinymce-height="600" class="form-control tinymce">
<textarea required name="descriptions[{{ $language['code'] }}][content]" data-tinymce-height="600" class="form-control tinymce">
{{ old('content', $descriptions[$language['code']]['content'] ?? '') }}
</textarea>
</div>

View File

@ -10,7 +10,7 @@
@if (session('success'))
<x-admin-alert type="success" msg="{{ session('success') }}" class="mt-4"/>
@endif
<form action="{{ admin_route('plugins.update', [$plugin->code]) }}" method="POST">
<form class="needs-validation" novalidate action="{{ admin_route('plugins.update', [$plugin->code]) }}" method="POST">
@csrf
{{ method_field('put') }}

View File

@ -24,7 +24,7 @@
<div class="card">
{{-- <div class="card-header"><h6 class="card-title">基础信息</h6></div> --}}
<div class="card-body">
<form action="{{ $product->id ? admin_route('products.update', $product) : admin_route('products.store') }}"
<form novalidate class="needs-validation" action="{{ $product->id ? admin_route('products.update', $product) : admin_route('products.store') }}"
method="POST" id="app">
@csrf
@method($product->id ? 'PUT' : 'POST')
@ -33,7 +33,7 @@
<div class="tab-content">
<div class="tab-pane fade show active" id="tab-basic">
<h6 class="border-bottom pb-3 mb-4">数据</h6>
<x-admin-form-input-locale :width="600" name="descriptions.*.name" title="名称" :value="$descriptions" required />
<x-admin-form-input-locale :width="600" name="descriptions.*.name" title="名称" :value="$descriptions" :required="true" />
<x-admin::form.row title="图片">
<draggable
element="div"

View File

@ -132,4 +132,9 @@ $alert-padding-y: 0.5rem;
a {
color: #212529;
}
}
.was-validated .form-control:valid, .form-control.is-valid {
border-color: $input-border-color;
background-image: none;
}

View File

@ -8,6 +8,7 @@ const base = document.querySelector('base').href;
import './product';
import './header'
import './bootstrap-validation'
$(document).ready(function ($) {
$(document).on('click', '.offcanvas-products-delete', function () {

View File

@ -0,0 +1,20 @@
// Example starter JavaScript for disabling form submissions if there are invalid fields
$(function () {
var forms = document.querySelectorAll(".needs-validation");
// Loop over them and prevent submission
Array.prototype.slice.call(forms).forEach(function (form) {
form.addEventListener(
"submit",
function (event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add("was-validated");
},
false
);
});
});

View File

@ -18,5 +18,5 @@ return [
'updated_success' => 'Updated Successfully!',
'created_success' => 'Created Successfully!',
'get_success' => 'Get Successfully!',
'error_required' => 'Please fill out the :name',
];

View File

@ -0,0 +1,15 @@
<?php
/**
* currency.php
*
* @copyright 2022 opencart.cn - All Rights Reserved
* @link http://www.guangdawangluo.com
* @author TL <mengwb@opencart.cn>
* @created 2022-07-28 17:21:38
* @modified 2022-07-28 17:21:38
*/
return [
'error_required' => '请填写 :name',
];

View File

@ -18,7 +18,7 @@
</nav>
<div class="row">
<x-shop-sidebar/>
<x-shop-sidebar />
<div class="col-12 col-md-9">
<div class="card h-min-600">
@ -26,17 +26,18 @@
<h5 class="card-title">修改个人信息</h5>
</div>
<div class="card-body h-600">
<form action="{{ shop_route('account.edit.update') }}" method="POST">
<form novalidate class="needs-validation" action="{{ shop_route('account.edit.update') }}" method="POST">
@csrf
{{ method_field('put') }}
@if (session('success'))
<x-shop-alert type="success" msg="{{ session('success') }}" class="mt-4"/>
<x-shop-alert type="success" msg="{{ session('success') }}" class="mt-4" />
@endif
<div class="bg-light rounded-3 p-4 mb-4" style="background: #f6f9fc;">
<div class="d-flex align-items-center">
<img class="rounded-3" id="avatar" src="{{ image_resize($customer->avatar, 200, 200) }}" width="90">
<img class="rounded-3" id="avatar" src="{{ image_resize($customer->avatar, 200, 200) }}"
width="90">
<div class="ps-3">
<label class="btn btn-light shadow-sm bg-body mb-2" data-toggle="tooltip" title="Change your avatar">
<i class="bi bi-arrow-repeat"></i> 修改头像
@ -50,17 +51,19 @@
<div class="row gx-4 gy-3">
<div class="col-sm-6">
<label class="form-label">名称</label>
<input class="form-control {{ $errors->has('name') ? 'is-invalid' : '' }}" type="text" name="name" value="{{ old('name', $customer->name ?? '') }}">
@if ($errors->has('name'))
<span class="invalid-feedback" role="alert">{{ $errors->first('name') }}</span>
@endif
<input class="form-control {{ $errors->has('name') ? 'is-invalid' : '' }}" type="text" name="name"
value="{{ old('name', $customer->name ?? '') }}" required>
<span class="invalid-feedback"
role="alert">{{ $errors->has('name') ? $errors->first('name') : __('common.error_required', ['name' => '名称']) }}</span>
{{-- @if ($errors->has('name'))@endif --}}
</div>
<div class="col-sm-6">
<label class="form-label">邮箱</label>
<input class="form-control {{ $errors->has('email') ? 'is-invalid' : '' }}" type="email" name="email" value="{{ old('name', $customer->email ?? '') }}">
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">{{ $errors->first('email') }}</span>
@endif
<input class="form-control {{ $errors->has('email') ? 'is-invalid' : '' }}" type="email"
name="email" value="{{ old('name', $customer->email ?? '') }}" required>
<span class="invalid-feedback"
role="alert">{{ $errors->has('email') ? $errors->first('email') : __('common.error_required', ['name' => '邮箱']) }}</span>
{{-- @if ($errors->has('email'))@endif --}}
</div>
<div class="col-12 mt-4">
<button class="btn btn-primary mt-sm-0" type="submit">提交</button>
@ -73,26 +76,27 @@
</div>
</div>
<div class="modal fade" id="modal" tabindex="-1" data-bs-backdrop="static" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">裁剪</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="img-container">
<img id="cropper-image" src="{{ image_resize('/') }}" class="img-fluid">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary cropper-crop">确定</button>
<div class="modal fade" id="modal" tabindex="-1" data-bs-backdrop="static" aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">裁剪</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="img-container">
<img id="cropper-image" src="{{ image_resize('/') }}" class="img-fluid">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary cropper-crop">确定</button>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('add-scripts')
@ -104,7 +108,7 @@
$('#update-btn').change(function(e) {
var files = e.target.files;
var done = function (url) {
var done = function(url) {
$(this).val('');
image.src = url;
$('#modal').modal('show');
@ -121,7 +125,7 @@
done(URL.createObjectURL(file));
} else if (FileReader) {
reader = new FileReader();
reader.onload = function (e) {
reader.onload = function(e) {
done(reader.result);
};
reader.readAsDataURL(file);
@ -129,12 +133,12 @@
}
});
$modal.on('shown.bs.modal', function () {
$modal.on('shown.bs.modal', function() {
cropper = new Cropper(image, {
aspectRatio: 1,
viewMode: 3,
});
}).on('hidden.bs.modal', function () {
}).on('hidden.bs.modal', function() {
cropper.destroy();
cropper = null;
});
@ -152,7 +156,7 @@
});
initialAvatarURL = avatar.src;
// avatar.src = canvas.toDataURL();
canvas.toBlob(function (blob) {
canvas.toBlob(function(blob) {
var formData = new FormData();
formData.append('file', blob, 'avatar.png');