Files
ai-web/resources/views/admin/settings/index.blade.php
jiangdong.cheng 56c685b579 配置功能完善
2026-02-12 15:37:49 +08:00

347 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('layouts.admin')
@section('title', '首页模块配置')
@section('head')
<style>
.module-card { border: 1px solid #d9e2f1; border-radius: .75rem; background: #fff; }
.module-card + .module-card { margin-top: 1rem; }
.module-items { display: grid; gap: .75rem; }
.module-item { border: 1px dashed #d6dfef; border-radius: .65rem; padding: .75rem; background: #f9fbff; }
.module-item-preview { width: 100%; max-height: 160px; object-fit: cover; border-radius: .5rem; border: 1px solid #dbe5f4; background: #fff; }
.module-key { font-size: .78rem; color: #64748b; }
.inline-add-form { border: 1px solid #e2e8f0; border-radius: .65rem; padding: .75rem; background: #f8fafc; }
.drag-handle { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; border: 1px solid #d8e1f1; border-radius: .45rem; color: #64748b; cursor: grab; background: #fff; }
.drag-handle:active { cursor: grabbing; }
.sortable-ghost { opacity: .45; }
.sortable-chosen { box-shadow: 0 12px 24px rgba(42, 64, 120, .16); }
</style>
@endsection
@section('content')
<div class="card module-card">
<div class="card-header">
<h3 class="card-title">首页模块配置AI工具集</h3>
</div>
<div class="card-body">
<p class="text-muted mb-3">支持配置模块标题、副标题、图片、链接、排序与展示数量。保存后首页立即生效。</p>
<form id="home-module-form" method="post" action="{{ route('admin.settings.update') }}" class="d-grid gap-4">
@csrf
@method('put')
<div class="js-module-list d-grid gap-4">
@foreach($modules as $module)
<section class="module-card p-3 js-module-entry" data-module-id="{{ $module->id }}">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
<div class="d-flex align-items-center gap-2">
<span class="drag-handle js-drag-handle-module" title="拖拽排序"><i class="bi bi-grip-vertical"></i></span>
<div>
<h4 class="mb-1">{{ $module->name }}</h4>
<div class="module-key">key: {{ $module->module_key }}</div>
</div>
</div>
<label class="form-check form-switch m-0">
<input class="form-check-input" type="checkbox" name="modules[{{ $module->id }}][enabled]" value="1" @checked($module->enabled)>
<span class="form-check-label">启用模块</span>
</label>
</div>
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">模块名称</label>
<input type="text" class="form-control" name="modules[{{ $module->id }}][name]" value="{{ old("modules.{$module->id}.name", $module->name) }}" required>
</div>
<div class="col-md-3">
<label class="form-label">标题</label>
<input type="text" class="form-control" name="modules[{{ $module->id }}][title]" value="{{ old("modules.{$module->id}.title", $module->title) }}">
</div>
<div class="col-md-4">
<label class="form-label">副标题</label>
<input type="text" class="form-control" name="modules[{{ $module->id }}][subtitle]" value="{{ old("modules.{$module->id}.subtitle", $module->subtitle) }}">
</div>
<div class="col-md-2">
<label class="form-label">排序</label>
<input type="number" min="0" max="9999" class="form-control js-module-order-input" name="modules[{{ $module->id }}][sort_order]" value="{{ old("modules.{$module->id}.sort_order", $module->sort_order) }}">
</div>
<div class="col-md-2">
<label class="form-label">数量上限</label>
<input type="number" min="1" max="30" class="form-control" name="modules[{{ $module->id }}][limit]" value="{{ old("modules.{$module->id}.limit", $module->limit) }}">
</div>
<div class="col-md-2">
<label class="form-label">更多链接类型</label>
@php($moduleLinkType = old("modules.{$module->id}.more_link_type", $module->more_link_type))
<select class="form-select" name="modules[{{ $module->id }}][more_link_type]">
<option value="" @selected($moduleLinkType === null || $moduleLinkType === '')></option>
<option value="route" @selected($moduleLinkType === 'route')>内部路由</option>
<option value="url" @selected($moduleLinkType === 'url')>自定义URL</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">更多链接目标</label>
<input type="text" class="form-control" name="modules[{{ $module->id }}][more_link_target]" value="{{ old("modules.{$module->id}.more_link_target", $module->more_link_target) }}" placeholder="route 名称或 URL / 站内路径">
</div>
<div class="col-md-2">
<label class="form-label">侧栏标题</label>
<input type="text" class="form-control" name="modules[{{ $module->id }}][extra][side_title]" value="{{ old("modules.{$module->id}.extra.side_title", data_get($module->extra, 'side_title')) }}">
</div>
<div class="col-md-4">
<label class="form-label">侧栏副标题</label>
<input type="text" class="form-control" name="modules[{{ $module->id }}][extra][side_subtitle]" value="{{ old("modules.{$module->id}.extra.side_subtitle", data_get($module->extra, 'side_subtitle')) }}">
</div>
</div>
@if(in_array($module->module_key, ['channel_cards', 'promo_banners'], true))
<hr class="my-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="mb-0">模块条目</h5>
<small class="text-muted">条目要求:标题、图片、链接必填</small>
</div>
<div class="module-items js-item-list" data-module-id="{{ $module->id }}">
@foreach($module->items as $item)
<div class="module-item js-item-entry" data-item-id="{{ $item->id }}">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="module-key">item: {{ $item->id }}</span>
<span class="drag-handle js-drag-handle-item" title="拖拽排序"><i class="bi bi-grip-vertical"></i></span>
</div>
<div class="row g-3">
<div class="col-md-2">
<label class="form-label">排序</label>
<input type="number" min="0" max="9999" class="form-control js-item-order-input" name="modules[{{ $module->id }}][items][{{ $item->id }}][sort_order]" value="{{ old("modules.{$module->id}.items.{$item->id}.sort_order", $item->sort_order) }}">
</div>
<div class="col-md-2 d-flex align-items-end">
<label class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" name="modules[{{ $module->id }}][items][{{ $item->id }}][enabled]" value="1" @checked(old("modules.{$module->id}.items.{$item->id}.enabled", $item->enabled))>
<span class="form-check-label">启用</span>
</label>
</div>
<div class="col-md-4">
<label class="form-label">标题</label>
<input type="text" class="form-control" name="modules[{{ $module->id }}][items][{{ $item->id }}][title]" value="{{ old("modules.{$module->id}.items.{$item->id}.title", $item->title) }}">
</div>
<div class="col-md-4">
<label class="form-label">副标题</label>
<input type="text" class="form-control" name="modules[{{ $module->id }}][items][{{ $item->id }}][subtitle]" value="{{ old("modules.{$module->id}.items.{$item->id}.subtitle", $item->subtitle) }}">
</div>
<div class="col-md-6">
<label class="form-label">图片路径(/storage/...</label>
<div class="input-group">
<input type="text" class="form-control" name="modules[{{ $module->id }}][items][{{ $item->id }}][image_path]" value="{{ old("modules.{$module->id}.items.{$item->id}.image_path", $item->image_path) }}" id="module-item-image-{{ $item->id }}" placeholder="/storage/markdown-images/...">
<button class="btn btn-outline-primary js-md-upload-btn" type="button" data-target="#module-item-image-{{ $item->id }}">上传图片</button>
</div>
</div>
<div class="col-md-3">
<label class="form-label">链接类型</label>
@php($itemLinkType = old("modules.{$module->id}.items.{$item->id}.link_type", $item->link_type))
<select class="form-select" name="modules[{{ $module->id }}][items][{{ $item->id }}][link_type]">
<option value="route" @selected($itemLinkType === 'route')>内部路由</option>
<option value="url" @selected($itemLinkType === 'url')>自定义URL</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">链接目标</label>
<input type="text" class="form-control" name="modules[{{ $module->id }}][items][{{ $item->id }}][link_target]" value="{{ old("modules.{$module->id}.items.{$item->id}.link_target", $item->link_target) }}" placeholder="如 tools.list 或 https://...">
</div>
<div class="col-md-6">
@if(!empty($item->image_path))
<img class="module-item-preview" src="{{ $item->image_path }}" alt="{{ $item->title ?: '预览图' }}">
@else
<div class="module-item-preview d-flex align-items-center justify-content-center text-muted">暂无图片</div>
@endif
</div>
<div class="col-md-6 d-flex align-items-end justify-content-end">
<button
class="btn btn-outline-danger js-delete-item"
type="button"
data-delete-url="{{ route('admin.settings.items.destroy', ['module' => $module, 'item' => $item]) }}"
>删除条目</button>
</div>
</div>
</div>
@endforeach
</div>
@endif
</section>
@endforeach
</div>
<div>
<button class="btn btn-primary" type="submit">保存全部配置</button>
</div>
</form>
</div>
</div>
@foreach($modules as $module)
@if(in_array($module->module_key, ['channel_cards', 'promo_banners'], true))
<div class="card module-card mt-3">
<div class="card-header">
<h3 class="card-title">新增条目 - {{ $module->name }}</h3>
</div>
<div class="card-body">
<form method="post" action="{{ route('admin.settings.items.store', $module) }}" class="row g-3 inline-add-form">
@csrf
<div class="col-md-3">
<label class="form-label">标题</label>
<input type="text" class="form-control" name="title" placeholder="条目标题">
</div>
<div class="col-md-3">
<label class="form-label">副标题</label>
<input type="text" class="form-control" name="subtitle" placeholder="条目副标题">
</div>
<div class="col-md-3">
<label class="form-label">图片</label>
<div class="input-group">
<input type="text" class="form-control" name="image_path" id="new-item-image-{{ $module->id }}" placeholder="/storage/...">
<button class="btn btn-outline-primary js-md-upload-btn" type="button" data-target="#new-item-image-{{ $module->id }}">上传</button>
</div>
</div>
<div class="col-md-3">
<label class="form-label">排序</label>
<input type="number" min="0" max="9999" class="form-control" name="sort_order" value="{{ (($module->items->max('sort_order') ?? 0) + 10) }}">
</div>
<div class="col-md-3">
<label class="form-label">链接类型</label>
<select class="form-select" name="link_type">
<option value="route">内部路由</option>
<option value="url">自定义URL</option>
</select>
</div>
<div class="col-md-7">
<label class="form-label">链接目标</label>
<input type="text" class="form-control" name="link_target" placeholder="如 tools.list 或 https://example.com">
</div>
<div class="col-md-2 d-flex align-items-end">
<label class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" name="enabled" value="1" checked>
<span class="form-check-label">启用</span>
</label>
</div>
<div class="col-12">
<div class="text-muted small mb-2">可选内部路由:
@foreach($routeOptions as $option)
<code class="me-1">{{ $option['value'] }}</code>
@endforeach
</div>
<button class="btn btn-outline-primary" type="submit">新增条目</button>
</div>
</form>
</div>
</div>
@endif
@endforeach
<div class="card module-card mt-3">
<div class="card-header">
<h3 class="card-title">内部路由参考</h3>
</div>
<div class="card-body">
<div class="row g-2">
@foreach($routeOptions as $option)
<div class="col-md-4">
<div class="border rounded p-2 d-flex justify-content-between align-items-center">
<span>{{ $option['name'] }}</span>
<code>{{ $option['value'] }}</code>
</div>
</div>
@endforeach
</div>
</div>
</div>
@endsection
@section('scripts')
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
<script>
(() => {
const moduleForm = document.getElementById('home-module-form');
const moduleList = document.querySelector('.js-module-list');
const syncModuleOrder = () => {
if (!moduleList) {
return;
}
moduleList.querySelectorAll('.js-module-entry').forEach((entry, index) => {
const input = entry.querySelector('.js-module-order-input');
if (input instanceof HTMLInputElement) {
input.value = String((index + 1) * 10);
}
});
};
const syncItemOrder = (list) => {
if (!(list instanceof HTMLElement)) {
return;
}
list.querySelectorAll('.js-item-entry').forEach((entry, index) => {
const input = entry.querySelector('.js-item-order-input');
if (input instanceof HTMLInputElement) {
input.value = String((index + 1) * 10);
}
});
};
if (window.Sortable && moduleList) {
new Sortable(moduleList, {
animation: 150,
handle: '.js-drag-handle-module',
draggable: '.js-module-entry',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
onEnd: syncModuleOrder,
});
document.querySelectorAll('.js-item-list').forEach((list) => {
new Sortable(list, {
animation: 150,
handle: '.js-drag-handle-item',
draggable: '.js-item-entry',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
onEnd: () => syncItemOrder(list),
});
syncItemOrder(list);
});
syncModuleOrder();
}
moduleForm?.addEventListener('submit', () => {
syncModuleOrder();
document.querySelectorAll('.js-item-list').forEach((list) => syncItemOrder(list));
});
document.querySelectorAll('.js-delete-item').forEach((button) => {
button.addEventListener('click', () => {
const action = button.getAttribute('data-delete-url');
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
if (!action || !token) {
return;
}
if (!window.confirm('确认删除该条目?')) {
return;
}
const form = document.createElement('form');
form.method = 'post';
form.action = action;
form.innerHTML = `<input type="hidden" name="_token" value="${token}"><input type="hidden" name="_method" value="delete">`;
document.body.appendChild(form);
form.submit();
});
});
})();
</script>
@endsection