This commit is contained in:
parent
ad5b6fb023
commit
77d3cc2ec3
@ -61,11 +61,29 @@ class NewsController extends Controller
|
|||||||
if ($request->filled('from')) { $q->where('open_datetime','>=',$request->input('from')); }
|
if ($request->filled('from')) { $q->where('open_datetime','>=',$request->input('from')); }
|
||||||
if ($request->filled('to')) { $q->where('open_datetime','<=',$request->input('to')); }
|
if ($request->filled('to')) { $q->where('open_datetime','<=',$request->input('to')); }
|
||||||
|
|
||||||
// 並び順:公開日時の降順 → 主キー降順
|
// {追加} 並び替え(ホワイトリスト)
|
||||||
$rows = $q->orderByDesc('open_datetime')
|
$sort = (string)$request->query('sort', '');
|
||||||
->orderByDesc($this->pk)
|
$dir = strtolower((string)$request->query('dir', 'desc'));
|
||||||
->paginate(20)
|
$dir = in_array($dir, ['asc','desc'], true) ? $dir : 'desc';
|
||||||
->withQueryString();
|
|
||||||
|
// 画面キー → 実カラム
|
||||||
|
$sortable = [
|
||||||
|
'id' => $this->pk, // ニュースID
|
||||||
|
'news' => 'news', // ニュース内容
|
||||||
|
'open_datetime' => 'open_datetime', // 公開日時
|
||||||
|
'mode' => 'mode', // 表示モード
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isset($sortable[$sort])) {
|
||||||
|
$q->orderBy($sortable[$sort], $dir);
|
||||||
|
} else {
|
||||||
|
// 既定:公開日時降順 → 主キー降順
|
||||||
|
$q->orderByDesc('open_datetime')
|
||||||
|
->orderByDesc($this->pk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ページング(現在のクエリを維持)
|
||||||
|
$rows = $q->paginate(20)->appends($request->except('page'));
|
||||||
|
|
||||||
return view('admin.news.list', compact('rows'));
|
return view('admin.news.list', compact('rows'));
|
||||||
}
|
}
|
||||||
@ -76,21 +94,29 @@ class NewsController extends Controller
|
|||||||
public function add(Request $request)
|
public function add(Request $request)
|
||||||
{
|
{
|
||||||
if ($request->isMethod('post')) {
|
if ($request->isMethod('post')) {
|
||||||
// 入力チェック
|
$messages = [
|
||||||
|
'required' => ':attribute は、必ず入力してください。',
|
||||||
|
'open_datetime.date_format' => '公開日時は :format 形式(YYYY-MM-DD HH:MM:SS)で入力してください。',
|
||||||
|
];
|
||||||
|
$attributes = [
|
||||||
|
'news' => 'ニュース内容',
|
||||||
|
'open_datetime' => '公開日時',
|
||||||
|
'mode' => '表示モード',
|
||||||
|
];
|
||||||
$v = $request->validate([
|
$v = $request->validate([
|
||||||
'news' => 'required|string',
|
'news' => 'required|string',
|
||||||
'open_datetime' => 'nullable|date_format:Y-m-d H:i:s',
|
'open_datetime' => 'required|date_format:Y-m-d H:i:s',
|
||||||
'link_url' => 'nullable|string|max:255',
|
'link_url' => 'nullable|string|max:255',
|
||||||
'image1_filename' => 'nullable|string|max:255',
|
'image1_filename' => 'nullable|string|max:255',
|
||||||
'image2_filename' => 'nullable|string|max:255',
|
'image2_filename' => 'nullable|string|max:255',
|
||||||
'mode' => 'required|integer|min:0|max:9',
|
'mode' => 'required|integer|min:0|max:9',
|
||||||
]);
|
], $messages, $attributes);
|
||||||
|
|
||||||
// 登録
|
// 登録
|
||||||
$now = now();
|
$now = now();
|
||||||
DB::table($this->table)->insert([
|
DB::table($this->table)->insert([
|
||||||
'news' => $v['news'],
|
'news' => $v['news'],
|
||||||
'open_datetime' => $v['open_datetime'] ?? null,
|
'open_datetime' => $v['open_datetime'],
|
||||||
'link_url' => $v['link_url'] ?? null,
|
'link_url' => $v['link_url'] ?? null,
|
||||||
'image1_filename' => $v['image1_filename'] ?? null,
|
'image1_filename' => $v['image1_filename'] ?? null,
|
||||||
'image2_filename' => $v['image2_filename'] ?? null,
|
'image2_filename' => $v['image2_filename'] ?? null,
|
||||||
@ -118,20 +144,28 @@ class NewsController extends Controller
|
|||||||
if (!$news) { abort(404); }
|
if (!$news) { abort(404); }
|
||||||
|
|
||||||
if ($request->isMethod('post')) {
|
if ($request->isMethod('post')) {
|
||||||
// 入力チェック
|
$messages = [
|
||||||
|
'required' => ':attribute は、必ず入力してください。',
|
||||||
|
'open_datetime.date_format' => '公開日時は :format 形式(YYYY-MM-DD HH:MM:SS)で入力してください。',
|
||||||
|
];
|
||||||
|
$attributes = [
|
||||||
|
'news' => 'ニュース内容',
|
||||||
|
'open_datetime' => '公開日時',
|
||||||
|
'mode' => '表示モード',
|
||||||
|
];
|
||||||
$v = $request->validate([
|
$v = $request->validate([
|
||||||
'news' => 'required|string',
|
'news' => 'required|string',
|
||||||
'open_datetime' => 'nullable|date_format:Y-m-d H:i:s',
|
'open_datetime' => 'required|date_format:Y-m-d H:i:s',
|
||||||
'link_url' => 'nullable|string|max:255',
|
'link_url' => 'nullable|string|max:255',
|
||||||
'image1_filename' => 'nullable|string|max:255',
|
'image1_filename' => 'nullable|string|max:255',
|
||||||
'image2_filename' => 'nullable|string|max:255',
|
'image2_filename' => 'nullable|string|max:255',
|
||||||
'mode' => 'required|integer|min:0|max:9',
|
'mode' => 'required|integer|min:0|max:9',
|
||||||
]);
|
], $messages, $attributes);
|
||||||
|
|
||||||
// 更新
|
// 更新
|
||||||
DB::table($this->table)->where($this->pk, $id)->update([
|
DB::table($this->table)->where($this->pk, $id)->update([
|
||||||
'news' => $v['news'],
|
'news' => $v['news'],
|
||||||
'open_datetime' => $v['open_datetime'] ?? null,
|
'open_datetime' => $v['open_datetime'],
|
||||||
'link_url' => $v['link_url'] ?? null,
|
'link_url' => $v['link_url'] ?? null,
|
||||||
'image1_filename' => $v['image1_filename'] ?? null,
|
'image1_filename' => $v['image1_filename'] ?? null,
|
||||||
'image2_filename' => $v['image2_filename'] ?? null,
|
'image2_filename' => $v['image2_filename'] ?? null,
|
||||||
|
|||||||
@ -6,12 +6,12 @@
|
|||||||
<div class="content-header">
|
<div class="content-header">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-lg-6"><h1 class="m-0 text-dark">ニュース新規作成</h1></div>
|
<div class="col-lg-6"><h1 class="m-0 text-dark">新規</h1></div>
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<ol class="breadcrumb float-sm-right text-sm">
|
<ol class="breadcrumb float-sm-right text-sm">
|
||||||
<li class="breadcrumb-item"><a href="{{ route('home') }}">ホーム</a></li>
|
<li class="breadcrumb-item"><a href="{{ route('home') }}">ホーム</a></li>
|
||||||
<li class="breadcrumb-item"><a href="{{ route('news') }}">最新ニュース登録</a></li>
|
<li class="breadcrumb-item"><a href="{{ route('news') }}">最新ニュース登録</a></li>
|
||||||
<li class="breadcrumb-item active">新規作成</li>
|
<li class="breadcrumb-item active">新規</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -36,39 +36,106 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="post" action="{{ route('news_add') }}" id="news-add-form">
|
<form method="post" action="{{ route('news_add') }}" id="news-add-form">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
|
{{-- 基本情報 --}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>ニュース内容 <span class="text-danger">*</span></label>
|
<label>ニュース内容 <span class="text-danger">*</span></label>
|
||||||
<textarea name="news" class="form-control" rows="8" required>{{ old('news') }}</textarea>
|
<textarea name="news" class="form-control" rows="8" required>{{ old('news') }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>公開日時(例:2025-08-13 09:00:00)</label>
|
<label>公開日時 <span class="text-danger">*</span></label>
|
||||||
<input type="text" name="open_datetime" class="form-control" placeholder="YYYY-MM-DD HH:MM:SS" value="{{ old('open_datetime') }}">
|
<input type="text" name="open_datetime" class="form-control" placeholder="YYYY-MM-DD HH:MM:SS"
|
||||||
|
value="{{ old('open_datetime') }}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>リンクURL</label>
|
<label>リンクURL</label>
|
||||||
<input type="text" name="link_url" class="form-control" value="{{ old('link_url') }}">
|
<input type="text" name="link_url" class="form-control" value="{{ old('link_url') }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group col-md-6">
|
{{-- 画像操作ボタン行:左=画像1、右=画像2 --}}
|
||||||
<label>画像1URL</label>
|
<div class="row mb-3">
|
||||||
<input type="text" name="image1_filename" class="form-control" value="{{ old('image1_filename') }}">
|
<div class="col-md-6 mb-2">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default" id="btn-image1-upload">画像1アップロード</button>
|
||||||
|
<button type="button" class="btn btn-default" id="btn-image1-clear">画像1削除</button>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="file-image1" accept="image/*" class="d-none">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-md-6">
|
<div class="col-md-6 mb-2">
|
||||||
<label>画像2URL</label>
|
<div class="btn-group">
|
||||||
<input type="text" name="image2_filename" class="form-control" value="{{ old('image2_filename') }}">
|
<button type="button" class="btn btn-default" id="btn-image2-upload">画像2アップロード</button>
|
||||||
|
<button type="button" class="btn btn-default" id="btn-image2-clear">画像2削除</button>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="file-image2" accept="image/*" class="d-none">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label>表示モード <span class="text-danger">*</span></label>
|
{{-- 画像ボックス:左=画像1、右=画像2 --}}
|
||||||
<select name="mode" class="form-control" required>
|
<div class="row">
|
||||||
<option value="1" @selected(old('mode','1')=='1')>公開</option>
|
<div class="col-md-6">
|
||||||
<option value="2" @selected(old('mode')=='2')>下書き</option>
|
<div class="imgbox mb-3">
|
||||||
<option value="0" @selected(old('mode')=='0')>非表示</option>
|
<div class="imgbox-title">画像1</div>
|
||||||
</select>
|
<div class="mb-2">
|
||||||
|
<label class="small text-muted d-block mb-1">画像1URLスペース</label>
|
||||||
|
<input type="text" name="image1_filename" id="image1_url" class="form-control" placeholder="http(s)://..." value="{{ old('image1_filename') }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="small text-muted d-block mb-1">画像1表示スペース</label>
|
||||||
|
<div class="imgbox-preview">
|
||||||
|
<img id="image1_preview" src="{{ old('image1_filename') }}" alt="" style="display: {{ old('image1_filename') ? 'block' : 'none' }};">
|
||||||
|
<span id="image1_placeholder" class="text-muted" style="display: {{ old('image1_filename') ? 'none' : 'block' }};">プレビューなし</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="imgbox mb-3">
|
||||||
|
<div class="imgbox-title">画像2</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="small text-muted d-block mb-1">画像2URLスペース</label>
|
||||||
|
<input type="text" name="image2_filename" id="image2_url" class="form-control" placeholder="http(s)://..." value="{{ old('image2_filename') }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="small text-muted d-block mb-1">画像2表示スペース</label>
|
||||||
|
<div class="imgbox-preview">
|
||||||
|
<img id="image2_preview" src="{{ old('image2_filename') }}" alt="" style="display: {{ old('image2_filename') ? 'block' : 'none' }};">
|
||||||
|
<span id="image2_placeholder" class="text-muted" style="display: {{ old('image2_filename') ? 'none' : 'block' }};">プレビューなし</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- 表示モード(デフォルト:非表示) --}}
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<label class="d-block">表示モード <span class="text-danger">*</span></label>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mode_draft" value="2" required
|
||||||
|
@checked(old('mode','0')=='2')>
|
||||||
|
<label class="form-check-label" for="mode_draft">下書き(非表示)</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mode_public" value="1"
|
||||||
|
@checked(old('mode','0')=='1')>
|
||||||
|
<label class="form-check-label" for="mode_public">公開</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mode_auto" value="3"
|
||||||
|
@checked(old('mode','0')=='3')>
|
||||||
|
<label class="form-check-label" for="mode_auto">自動公開</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mode_hidden" value="0"
|
||||||
|
@checked(old('mode','0')=='0')>
|
||||||
|
<label class="form-check-label" for="mode_hidden">非表示</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<a href="{{ route('news') }}" class="btn btn-outline-secondary">戻る</a>
|
<a href="{{ route('news') }}" class="btn btn-default">戻る</a>
|
||||||
<button type="button" class="btn btn-primary" id="register-btn">登録</button>
|
<!-- 変更点: class="register" を付与して app.js の汎用確認を使用 -->
|
||||||
|
<button type="button" class="btn btn-default register" id="register-btn">登録</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -77,13 +144,60 @@
|
|||||||
</section>
|
</section>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
/* 画像ボックスの体裁 */
|
||||||
|
.imgbox { border:1px solid #ced4da; border-radius:.25rem; }
|
||||||
|
.imgbox-title { background:#f4f6f9; border-bottom:1px solid #ced4da; padding:.5rem .75rem; font-weight:600; }
|
||||||
|
.imgbox-preview {
|
||||||
|
border:1px solid #ced4da; border-radius:.25rem; background:#f8f9fa;
|
||||||
|
height:200px; display:flex; align-items:center; justify-content:center; overflow:hidden;
|
||||||
|
}
|
||||||
|
.imgbox-preview img { max-width:100%; max-height:100%; display:block; }
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script>
|
<script>
|
||||||
// 登録ボタン押下時に確認ダイアログ
|
// 画像プレビュー系のみ(確認ダイアログは app.js に委譲)
|
||||||
document.getElementById('register-btn').addEventListener('click', function(e) {
|
(function(){
|
||||||
if (confirm('登録してよろしいですか?')) {
|
function bindUrlPreview(urlId, imgId, phId) {
|
||||||
document.getElementById('news-add-form').submit();
|
const url = document.getElementById(urlId);
|
||||||
|
const img = document.getElementById(imgId);
|
||||||
|
const ph = document.getElementById(phId);
|
||||||
|
if (!url || !img || !ph) return;
|
||||||
|
function update(){
|
||||||
|
const v = (url.value || '').trim();
|
||||||
|
if (v) { img.src = v; img.style.display='block'; ph.style.display='none'; }
|
||||||
|
else { img.removeAttribute('src'); img.style.display='none'; ph.style.display='block'; }
|
||||||
|
}
|
||||||
|
url.addEventListener('input', update); update();
|
||||||
}
|
}
|
||||||
});
|
function bindUpload(btnId, fileId, urlId, imgId, phId, clearBtnId){
|
||||||
|
const btn = document.getElementById(btnId);
|
||||||
|
const file= document.getElementById(fileId);
|
||||||
|
const url = document.getElementById(urlId);
|
||||||
|
const img = document.getElementById(imgId);
|
||||||
|
const ph = document.getElementById(phId);
|
||||||
|
const clr = document.getElementById(clearBtnId);
|
||||||
|
if (!btn || !file || !url || !img || !ph || !clr) return;
|
||||||
|
btn.addEventListener('click', ()=> file.click());
|
||||||
|
file.addEventListener('change', ()=>{
|
||||||
|
const f = file.files && file.files[0]; if (!f) return;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = e => { img.src = e.target.result; img.style.display='block'; ph.style.display='none'; };
|
||||||
|
reader.readAsDataURL(f);
|
||||||
|
});
|
||||||
|
clr.addEventListener('click', ()=>{
|
||||||
|
file.value=''; url.value='';
|
||||||
|
img.removeAttribute('src'); img.style.display='none'; ph.style.display='block';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bindUrlPreview('image1_url','image1_preview','image1_placeholder');
|
||||||
|
bindUrlPreview('image2_url','image2_preview','image2_placeholder');
|
||||||
|
bindUpload('btn-image1-upload','file-image1','image1_url','image1_preview','image1_placeholder','btn-image1-clear');
|
||||||
|
bindUpload('btn-image2-upload','file-image2','image2_url','image2_preview','image2_placeholder','btn-image2-clear');
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
@section('title', 'ニュース編集')
|
@section('title', '編集')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
{{-- ▼ コンテンツヘッダー(パンくず) --}}
|
{{-- ▼ コンテンツヘッダー(パンくず) --}}
|
||||||
<div class="content-header">
|
<div class="content-header">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-lg-6"><h1 class="m-0 text-dark">ニュース編集</h1></div>
|
<div class="col-lg-6"><h1 class="m-0 text-dark">編集</h1></div>
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<ol class="breadcrumb float-sm-right text-sm">
|
<ol class="breadcrumb float-sm-right text-sm">
|
||||||
<li class="breadcrumb-item"><a href="{{ route('home') }}">ホーム</a></li>
|
<li class="breadcrumb-item"><a href="{{ route('home') }}">ホーム</a></li>
|
||||||
@ -27,55 +27,216 @@
|
|||||||
<div class="alert alert-danger"><ul class="mb-0">@foreach($errors->all() as $e)<li>{{ $e }}</li>@endforeach</ul></div>
|
<div class="alert alert-danger"><ul class="mb-0">@foreach($errors->all() as $e)<li>{{ $e }}</li>@endforeach</ul></div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
{{-- ▼ 入力フォーム --}}
|
{{-- ▼ 入力フォーム(新規画面と同一レイアウト) --}}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="post" action="{{ route('news_edit', ['id'=>$news->news_id ?? $news->id]) }}">
|
<!-- 変更点: フォームIDを app.js に合わせる -->
|
||||||
|
<form method="post" action="{{ route('news_edit', ['id'=>$news->news_id ?? $news->id]) }}" id="form_edit">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
|
{{-- 基本情報 --}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>ニュース内容 <span class="text-danger">*</span></label>
|
<label>ニュース内容 <span class="text-danger">*</span></label>
|
||||||
<textarea name="news" class="form-control" rows="8" required>{{ old('news', $news->news) }}</textarea>
|
<textarea name="news" class="form-control" rows="8" required>{{ old('news', $news->news) }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>公開日時</label>
|
<label>公開日時 <span class="text-danger">*</span></label>
|
||||||
<input type="text" name="open_datetime" class="form-control" placeholder="YYYY-MM-DD HH:MM:SS"
|
<input type="text" name="open_datetime" class="form-control" placeholder="YYYY-MM-DD HH:MM:SS"
|
||||||
value="{{ old('open_datetime', $news->open_datetime) }}">
|
value="{{ old('open_datetime', $news->open_datetime) }}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>リンクURL</label>
|
<label>リンクURL</label>
|
||||||
<input type="text" name="link_url" class="form-control" value="{{ old('link_url', $news->link_url) }}">
|
<input type="text" name="link_url" class="form-control" value="{{ old('link_url', $news->link_url) }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group col-md-6">
|
{{-- 画像操作ボタン行:左=画像1、右=画像2(btn-default に統一) --}}
|
||||||
<label>画像1URL</label>
|
<div class="row mb-3">
|
||||||
<input type="text" name="image1_filename" class="form-control" value="{{ old('image1_filename', $news->image1_filename) }}">
|
<div class="col-md-6 mb-2">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default" id="btn-image1-upload">画像1アップロード</button>
|
||||||
|
<button type="button" class="btn btn-default" id="btn-image1-clear">画像1削除</button>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="file-image1" accept="image/*" class="d-none">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-md-6">
|
<div class="col-md-6 mb-2">
|
||||||
<label>画像2URL</label>
|
<div class="btn-group">
|
||||||
<input type="text" name="image2_filename" class="form-control" value="{{ old('image2_filename', $news->image2_filename) }}">
|
<button type="button" class="btn btn-default" id="btn-image2-upload">画像2アップロード</button>
|
||||||
|
<button type="button" class="btn btn-default" id="btn-image2-clear">画像2削除</button>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="file-image2" accept="image/*" class="d-none">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label>表示モード <span class="text-danger">*</span></label>
|
{{-- 画像ボックス:左=画像1、右=画像2 --}}
|
||||||
<select name="mode" class="form-control" required>
|
<div class="row">
|
||||||
<option value="1" @selected(old('mode', $news->mode)==1)>公開</option>
|
<div class="col-md-6">
|
||||||
<option value="2" @selected(old('mode', $news->mode)==2)>下書き</option>
|
<div class="imgbox mb-3">
|
||||||
<option value="0" @selected(old('mode', $news->mode)==0)>非表示</option>
|
<div class="imgbox-title">画像1</div>
|
||||||
</select>
|
<div class="mb-2">
|
||||||
|
<label class="small text-muted d-block mb-1">画像1URLスペース</label>
|
||||||
|
<input type="text" name="image1_filename" id="image1_url" class="form-control" placeholder="http(s)://..."
|
||||||
|
value="{{ old('image1_filename', $news->image1_filename) }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="small text-muted d-block mb-1">画像1表示スペース</label>
|
||||||
|
<div class="imgbox-preview">
|
||||||
|
<img id="image1_preview" src="{{ old('image1_filename', $news->image1_filename) }}" alt=""
|
||||||
|
style="display: {{ old('image1_filename', $news->image1_filename) ? 'block' : 'none' }};">
|
||||||
|
<span id="image1_placeholder" class="text-muted"
|
||||||
|
style="display: {{ old('image1_filename', $news->image1_filename) ? 'none' : 'block' }};">プレビューなし</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="imgbox mb-3">
|
||||||
|
<div class="imgbox-title">画像2</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="small text-muted d-block mb-1">画像2URLスペース</label>
|
||||||
|
<input type="text" name="image2_filename" id="image2_url" class="form-control" placeholder="http(s)://..."
|
||||||
|
value="{{ old('image2_filename', $news->image2_filename) }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="small text-muted d-block mb-1">画像2表示スペース</label>
|
||||||
|
<div class="imgbox-preview">
|
||||||
|
<img id="image2_preview" src="{{ old('image2_filename', $news->image2_filename) }}" alt=""
|
||||||
|
style="display: {{ old('image2_filename', $news->image2_filename) ? 'block' : 'none' }};">
|
||||||
|
<span id="image2_placeholder" class="text-muted"
|
||||||
|
style="display: {{ old('image2_filename', $news->image2_filename) ? 'none' : 'block' }};">プレビューなし</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- 表示モード(ラジオ・必須/新規画面と同一UI) --}}
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<label class="d-block">表示モード <span class="text-danger">*</span></label>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mode_draft" value="2" required
|
||||||
|
@checked(old('mode', (string)$news->mode)=='2')>
|
||||||
|
<label class="form-check-label" for="mode_draft">下書き(非表示)</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mode_public" value="1"
|
||||||
|
@checked(old('mode', (string)$news->mode)=='1')>
|
||||||
|
<label class="form-check-label" for="mode_public">公開</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mode_auto" value="3"
|
||||||
|
@checked(old('mode', (string)$news->mode)=='3')>
|
||||||
|
<label class="form-check-label" for="mode_auto">自動公開</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mode_hidden" value="0"
|
||||||
|
@checked(old('mode', (string)$news->mode)=='0')>
|
||||||
|
<label class="form-check-label" for="mode_hidden">非表示</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<a href="{{ route('news') }}" class="btn btn-outline-secondary">一覧に戻る</a>
|
<a href="{{ route('news') }}" class="btn btn-default">戻る</a>
|
||||||
<button class="btn btn-primary">保存</button>
|
<button type="button" class="btn btn-default" id="register_edit">保存</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- ▼ 登録/更新メタ情報 --}}
|
|
||||||
<div class="text-muted small">
|
|
||||||
<div>登録日時:{{ $news->created_at }}</div>
|
|
||||||
<div>更新日時:{{ $news->updated_at }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
/* 画像ボックス(新規画面と同一) */
|
||||||
|
.imgbox { border:1px solid #ced4da; border-radius:.25rem; }
|
||||||
|
.imgbox-title { background:#f4f6f9; border-bottom:1px solid #ced4da; padding:.5rem .75rem; font-weight:600; }
|
||||||
|
.imgbox-preview {
|
||||||
|
border:1px solid #ced4da; border-radius:.25rem; background:#f8f9fa;
|
||||||
|
height:200px; display:flex; align-items:center; justify-content:center; overflow:hidden;
|
||||||
|
}
|
||||||
|
.imgbox-preview img { max-width:100%; max-height:100%; display:block; }
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
// 画像プレビュー系のみ(確認ダイアログは app.js に委譲)
|
||||||
|
(function(){
|
||||||
|
function bindUrlPreview(urlId, imgId, phId) {
|
||||||
|
const url = document.getElementById(urlId);
|
||||||
|
const img = document.getElementById(imgId);
|
||||||
|
const ph = document.getElementById(phId);
|
||||||
|
if (!url || !img || !ph) return;
|
||||||
|
function update(){
|
||||||
|
const v = (url.value || '').trim();
|
||||||
|
if (v) { img.src = v; img.style.display='block'; ph.style.display='none'; }
|
||||||
|
else { img.removeAttribute('src'); img.style.display='none'; ph.style.display='block'; }
|
||||||
|
}
|
||||||
|
url.addEventListener('input', update); update();
|
||||||
|
}
|
||||||
|
function bindUpload(btnId, fileId, urlId, imgId, phId, clearBtnId){
|
||||||
|
const btn = document.getElementById(btnId);
|
||||||
|
const file= document.getElementById(fileId);
|
||||||
|
const url = document.getElementById(urlId);
|
||||||
|
const img = document.getElementById(imgId);
|
||||||
|
const ph = document.getElementById(phId);
|
||||||
|
const clr = document.getElementById(clearBtnId);
|
||||||
|
if (!btn || !file || !url || !img || !ph || !clr) return;
|
||||||
|
btn.addEventListener('click', ()=> file.click());
|
||||||
|
file.addEventListener('change', ()=>{
|
||||||
|
const f = file.files && file.files[0]; if (!f) return;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = e => { img.src = e.target.result; img.style.display='block'; ph.style.display='none'; };
|
||||||
|
reader.readAsDataURL(f);
|
||||||
|
});
|
||||||
|
clr.addEventListener('click', ()=>{
|
||||||
|
file.value=''; url.value='';
|
||||||
|
img.removeAttribute('src'); img.style.display='none'; ph.style.display='block';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bindUrlPreview('image1_url','image1_preview','image1_placeholder');
|
||||||
|
bindUrlPreview('image2_url','image2_preview','image2_placeholder');
|
||||||
|
bindUpload('btn-image1-upload','file-image1','image1_url','image1_preview','image1_placeholder','btn-image1-clear');
|
||||||
|
bindUpload('btn-image2-upload','file-image2','image2_url','image2_preview','image2_placeholder','btn-image2-clear');
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
/* 保存ボタン:確認 → 送信(app.js なしでも動く) */
|
||||||
|
(function () {
|
||||||
|
const btn = document.getElementById('register_edit');
|
||||||
|
const form = document.getElementById('form_edit');
|
||||||
|
if (!btn || !form) return;
|
||||||
|
|
||||||
|
btn.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// HTML5必須チェックを先に実行
|
||||||
|
if (typeof form.reportValidity === 'function' && !form.reportValidity()) return;
|
||||||
|
|
||||||
|
function submitNow() {
|
||||||
|
btn.disabled = true; // 二重送信防止
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.jQuery && window.$ && $.confirm) {
|
||||||
|
$.confirm({
|
||||||
|
title: '確認ダイアログ。',
|
||||||
|
content: '登録してよろしいですか? はい/いいえ',
|
||||||
|
buttons: {
|
||||||
|
ok: { text: 'はい', btnClass: 'btn-primary', action: submitNow },
|
||||||
|
いいえ: function () {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (confirm('登録してよろしいですか?')) submitNow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|||||||
@ -3,8 +3,22 @@
|
|||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@php
|
@php
|
||||||
// ▼ 表示モードのラベル
|
|
||||||
$modeLabel = [0=>'非表示', 1=>'公開', 2=>'下書き', 3=>'自動公開'];
|
$modeLabel = [0=>'非表示', 1=>'公開', 2=>'下書き', 3=>'自動公開'];
|
||||||
|
|
||||||
|
$curSort = request('sort');
|
||||||
|
$curDir = strtolower(request('dir', 'desc'));
|
||||||
|
|
||||||
|
// DataTables と同一のクラスを付与(未選択: sorting / 昇順: sorting_asc / 降順: sorting_desc)
|
||||||
|
$thClass = function(string $key) use ($curSort, $curDir) {
|
||||||
|
if ($curSort !== $key) return 'sorting';
|
||||||
|
return $curDir === 'asc' ? 'sorting_asc' : 'sorting_desc';
|
||||||
|
};
|
||||||
|
|
||||||
|
// クリック時に昇降をトグル
|
||||||
|
$urlFor = function(string $key) use ($curSort, $curDir) {
|
||||||
|
$next = ($curSort === $key && $curDir === 'asc') ? 'desc' : 'asc';
|
||||||
|
return route('news', array_merge(request()->except('page'), ['sort'=>$key, 'dir'=>$next]));
|
||||||
|
};
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
{{-- ▼ コンテンツヘッダー(パンくず) --}}
|
{{-- ▼ コンテンツヘッダー(パンくず) --}}
|
||||||
@ -26,41 +40,62 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
|
||||||
{{-- ▼ フラッシュメッセージ --}}
|
{{-- ▼ フラッシュメッセージ --}}
|
||||||
@if(session('success')) <div class="alert alert-success">{{ session('success') }}</div> @endif
|
@if(session('success'))
|
||||||
@if(session('error')) <div class="alert alert-danger">{{ session('error') }}</div> @endif
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
{{ session('success') }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
{{ session('error') }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|
||||||
{{-- ▼ ヘッダー:新規/削除 --}}
|
{{-- ▼ ヘッダー:新規/削除 --}}
|
||||||
<div class="card-header d-flex align-items-center">
|
<div class="card-header d-flex align-items-center">
|
||||||
<a href="{{ route('news_add') }}" class="btn btn-primary mr-2">新規</a>
|
<a href="{{ route('news_add') }}" class="btn btn-default mr-2">新規</a>
|
||||||
<button form="news-list-form" type="submit" class="btn btn-outline-secondary"
|
<button type="button" class="btn btn-default" id="delete_edit">削除</button>
|
||||||
onclick="return confirm('選択した項目を削除しますか?')">削除</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- ▼ 一覧テーブル --}}
|
{{-- ▼ 一覧テーブル --}}
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<form id="news-list-form" method="POST" action="{{ route('news_delete') }}">
|
<form id="form_delete" method="POST" action="{{ route('news_delete') }}">
|
||||||
@csrf
|
@csrf
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-bordered text-nowrap mb-0 table-news"> {{-- ← table-striped は使わない --}}
|
<table class="table table-bordered text-nowrap mb-0 table-news dataTable">
|
||||||
<thead class="thead-light">
|
<thead class="thead-light">
|
||||||
<tr>
|
<tr>
|
||||||
{{-- 統合列:ヘッダーも同色 --}}
|
|
||||||
<th class="col-actions">
|
<th class="col-actions">
|
||||||
<div class="actions-wrap">
|
<div class="actions-wrap">
|
||||||
<input type="checkbox" id="check-all">
|
<input type="checkbox" id="check-all">
|
||||||
<span class="text-muted" style="font-weight:normal;"></span>
|
<span class="text-muted" style="font-weight:normal;"></span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
{{-- データ列 --}}
|
|
||||||
<th class="w-id">ニュースID</th>
|
{{-- ▼ 並び替え可能カラム(DataTables と同じクラスを使用) --}}
|
||||||
<th>ニュース内容</th>
|
<th class="w-id {{ $thClass('id') }}">
|
||||||
<th class="w-datetime">公開日時</th>
|
<a href="{{ $urlFor('id') }}" class="header-link">ニュースID</a>
|
||||||
|
</th>
|
||||||
|
<th class="{{ $thClass('news') }}">
|
||||||
|
<a href="{{ $urlFor('news') }}" class="header-link">ニュース内容</a>
|
||||||
|
</th>
|
||||||
|
<th class="w-datetime {{ $thClass('open_datetime') }}">
|
||||||
|
<a href="{{ $urlFor('open_datetime') }}" class="header-link">公開日時</a>
|
||||||
|
</th>
|
||||||
<th class="w-url">リンクURL</th>
|
<th class="w-url">リンクURL</th>
|
||||||
<th class="w-img">画像1URL</th>
|
<th class="w-img">画像1URL</th>
|
||||||
<th class="w-img">画像2URL</th>
|
<th class="w-img">画像2URL</th>
|
||||||
<th class="w-mode">表示モード</th>
|
<th class="w-mode {{ $thClass('mode') }}">
|
||||||
|
<a href="{{ $urlFor('mode') }}" class="header-link">表示モード</a>
|
||||||
|
</th>
|
||||||
<th class="w-created">登録日時</th>
|
<th class="w-created">登録日時</th>
|
||||||
<th class="w-updated">更新日時</th>
|
<th class="w-updated">更新日時</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -109,12 +144,76 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{-- ▼ スクリプト:全選択/全解除 --}}
|
<!-- jQuery Confirm(存在すれば使用。無ければ下のスクリプトで標準confirmへフォールバック) -->
|
||||||
|
<link rel="stylesheet" href="{{ asset('plugins/jquery-confirm/jquery-confirm.min.css') }}">
|
||||||
|
<script src="{{ asset('plugins/jquery-confirm/jquery-confirm.min.js') }}"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 見出しの文字色を黒(リンクも黒・下線なし) */
|
||||||
|
.table-news thead th .header-link { color:#212529!important;text-decoration:none!important;display:block;white-space:nowrap;padding-right:1.8rem; }
|
||||||
|
/* DataTables の矢印位置を統一 */
|
||||||
|
.table-news.dataTable thead th.sorting,
|
||||||
|
.table-news.dataTable thead th.sorting_asc,
|
||||||
|
.table-news.dataTable thead th.sorting_desc { background-repeat:no-repeat;background-position:right .6rem center!important;padding-right:1.8rem; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
(function(){
|
||||||
|
// 全選択
|
||||||
document.getElementById('check-all')?.addEventListener('change', function(e){
|
document.getElementById('check-all')?.addEventListener('change', function(e){
|
||||||
document.querySelectorAll('input[name="ids[]"]').forEach(function(el){
|
document.querySelectorAll('input[name="ids[]"]').forEach(function(el){ el.checked = e.target.checked; });
|
||||||
el.checked = e.target.checked;
|
});
|
||||||
|
|
||||||
|
// 共通: 未選択アラート
|
||||||
|
function showNoSelection(){
|
||||||
|
if (window.jQuery && window.$ && $.confirm) {
|
||||||
|
$.confirm({
|
||||||
|
title: '確認ダイアログ。',
|
||||||
|
content: '!削除対象が選択されていません。はい/いいえ',
|
||||||
|
buttons: {
|
||||||
|
ok: { text: 'はい', btnClass: 'btn-primary' },
|
||||||
|
いいえ: function () {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('削除対象が選択されていません。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 共通: 削除確認ダイアログ
|
||||||
|
function showDeleteConfirm(onYes){
|
||||||
|
if (window.jQuery && window.$ && $.confirm) {
|
||||||
|
$.confirm({
|
||||||
|
title: '削除ダイアログ',
|
||||||
|
content: '!削除してよろしいですか?',
|
||||||
|
buttons: {
|
||||||
|
ok: { text: 'はい', btnClass: 'btn-primary', action: function(){ if (typeof onYes==='function') onYes(); } },
|
||||||
|
いいえ: function () {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (confirm('削除してよろしいですか?')) { if (typeof onYes==='function') onYes(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 削除ボタン(#delete_edit)— 選択確認 → ダイアログ → 提出
|
||||||
|
document.getElementById('delete_edit')?.addEventListener('click', function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
const checked = document.querySelectorAll('input[name="ids[]"]:checked').length;
|
||||||
|
if (!checked) return showNoSelection();
|
||||||
|
|
||||||
|
const form = document.getElementById('form_delete');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
showDeleteConfirm(function(){
|
||||||
|
form.submit();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// フラッシュ自動閉じ
|
||||||
|
setTimeout(function(){
|
||||||
|
document.querySelectorAll('.alert-dismissible')?.forEach(function(el){ el.classList.remove('show'); el.style.display='none'; });
|
||||||
|
}, 5000);
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user