|
|
@@ -34,7 +34,7 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="panel-body" style="padding: 20px;">
|
|
|
- <form method="POST" action="{{ route('admin.dynamicmenu.update', $menuItem->id) }}">
|
|
|
+ <form method="POST" action="{{ route('admin.dynamicmenu.update', $menuItem->id) }}" id="menuForm">
|
|
|
@csrf
|
|
|
@method('PUT')
|
|
|
|
|
|
@@ -45,6 +45,7 @@
|
|
|
</label>
|
|
|
<input type="text"
|
|
|
name="name"
|
|
|
+ id="nameInput"
|
|
|
class="control"
|
|
|
value="{{ old('name', $menuItem->name) }}"
|
|
|
required
|
|
|
@@ -53,16 +54,21 @@
|
|
|
</div>
|
|
|
|
|
|
{{-- Key 字段 --}}
|
|
|
- <div class="form-group">
|
|
|
- <label class="required">Key</label>
|
|
|
+ <div class="form-group" style="margin-bottom: 20px;">
|
|
|
+ <label class="required" style="display: block; margin-bottom: 5px; font-weight: 500;">
|
|
|
+ Key <span style="color: red;">*</span>
|
|
|
+ </label>
|
|
|
<input type="text"
|
|
|
- name="key"
|
|
|
- class="control"
|
|
|
- value="{{ old('key', $menuItem->key ?? '') }}"
|
|
|
- required
|
|
|
- placeholder="例如:settings.my-menu">
|
|
|
- <small class="control-info">
|
|
|
- 建议以 'settings.' 开头,这样菜单会显示在 Settings 下。例如:settings.dynamic-menu
|
|
|
+ name="key"
|
|
|
+ id="keyInput"
|
|
|
+ class="control"
|
|
|
+ value="{{ old('key', $menuItem->key ?? '') }}"
|
|
|
+ required
|
|
|
+ placeholder="例如:settings.my-menu"
|
|
|
+ style="width: 100%; padding: 8px 12px; border: 1px solid #dee2e6; border-radius: 4px; font-size: 14px;">
|
|
|
+ <small class="control-info" style="display: block; margin-top: 5px; font-size: 12px; color: #6c757d;">
|
|
|
+ <strong style="color: #dc3545;">重要提示:</strong>
|
|
|
+ 如果想让菜单显示在 Settings 下,Key 必须以 <strong>settings.</strong> 开头。例如:settings.dynamic-menu
|
|
|
</small>
|
|
|
</div>
|
|
|
|
|
|
@@ -71,13 +77,15 @@
|
|
|
<label style="display: block; margin-bottom: 5px; font-weight: 500;">URL/路由</label>
|
|
|
<input type="text"
|
|
|
name="route"
|
|
|
+ id="routeInput"
|
|
|
class="control"
|
|
|
value="{{ old('route', $menuItem->route) }}"
|
|
|
placeholder="例如:admin.dashboard"
|
|
|
style="width: 100%; padding: 8px 12px; border: 1px solid #dee2e6; border-radius: 4px; font-size: 14px;">
|
|
|
<small class="control-info" style="display: block; margin-top: 5px; font-size: 12px; color: #6c757d;">
|
|
|
- 可以是相对路径、完整URL或路由名称
|
|
|
+ 可以是路由名称(如:admin.dashboard)或完整URL
|
|
|
</small>
|
|
|
+ <div id="routeValidationResult" style="margin-top: 5px; font-size: 12px;"></div>
|
|
|
</div>
|
|
|
|
|
|
{{-- 图标 --}}
|
|
|
@@ -97,16 +105,16 @@
|
|
|
{{-- 父级菜单 --}}
|
|
|
<div class="form-group" style="margin-bottom: 20px;">
|
|
|
<label style="display: block; margin-bottom: 5px; font-weight: 500;">父级菜单</label>
|
|
|
- <select name="parent_id" class="control" style="width: 100%; padding: 8px 12px; border: 1px solid #dee2e6; border-radius: 4px; font-size: 14px;">
|
|
|
+ <select name="parent_id" id="parentSelect" class="control" style="width: 100%; padding: 8px 12px; border: 1px solid #dee2e6; border-radius: 4px; font-size: 14px;">
|
|
|
<option value="">作为顶级菜单</option>
|
|
|
@foreach($menuItems as $menuItemOption)
|
|
|
- @if($menuItemOption->id != $menuItem->id) {{-- 不能选择自己作为父级 --}}
|
|
|
- <option value="{{ $menuItemOption->id }}" {{ old('parent_id', $menuItem->parent_id) == $menuItemOption->id ? 'selected' : '' }}>
|
|
|
- {{ $menuItemOption->name }}
|
|
|
- @if($menuItemOption->children && $menuItemOption->children->count() > 0)
|
|
|
- (有子菜单)
|
|
|
- @endif
|
|
|
- </option>
|
|
|
+ @if($menuItemOption->id != $menuItem->id)
|
|
|
+ <option value="{{ $menuItemOption->id }}" {{ old('parent_id', $menuItem->parent_id) == $menuItemOption->id ? 'selected' : '' }}>
|
|
|
+ {{ $menuItemOption->name }}
|
|
|
+ @if($menuItemOption->children && $menuItemOption->children->count() > 0)
|
|
|
+ (有子菜单)
|
|
|
+ @endif
|
|
|
+ </option>
|
|
|
@endif
|
|
|
@endforeach
|
|
|
</select>
|
|
|
@@ -155,25 +163,255 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- {{-- 引入必要的JavaScript --}}
|
|
|
+ {{-- JavaScript 代码 - 使用事件委托 --}}
|
|
|
@push('scripts')
|
|
|
- <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
|
|
- <script>
|
|
|
- /*$(document).ready(function() {
|
|
|
- // 自动生成key
|
|
|
- $('input[name="name"]').on('blur', function() {
|
|
|
- var name = $(this).val();
|
|
|
- var keyInput = $('input[name="key"]');
|
|
|
-
|
|
|
- if (keyInput.val() === '') {
|
|
|
- var key = name.toLowerCase()
|
|
|
- .replace(/\s+/g, '-')
|
|
|
- .replace(/[^a-z0-9-]/g, '');
|
|
|
- // 默认添加到 settings 下
|
|
|
- keyInput.val('settings.' + key);
|
|
|
+ <script>
|
|
|
+ (function() {
|
|
|
+ console.log('DynamicMenu Edit: Script loaded');
|
|
|
+
|
|
|
+ // 等待元素出现
|
|
|
+ let checkInterval = setInterval(function() {
|
|
|
+ const routeInput = document.getElementById('routeInput');
|
|
|
+ if (routeInput) {
|
|
|
+ clearInterval(checkInterval);
|
|
|
+ console.log('DynamicMenu Edit: routeInput found!');
|
|
|
+ initValidation(routeInput);
|
|
|
+ }
|
|
|
+ }, 100);
|
|
|
+
|
|
|
+ function initValidation(routeInput) {
|
|
|
+ const validationResult = document.getElementById('routeValidationResult');
|
|
|
+ const menuForm = document.getElementById('menuForm');
|
|
|
+ const nameInput = document.getElementById('nameInput');
|
|
|
+ const keyInput = document.getElementById('keyInput');
|
|
|
+
|
|
|
+ let validationTimeout = null;
|
|
|
+ let lastValidatedRoute = '';
|
|
|
+ let routeIsValid = false;
|
|
|
+
|
|
|
+ function getCsrfToken() {
|
|
|
+ const tokenMeta = document.querySelector('meta[name="csrf-token"]');
|
|
|
+ if (tokenMeta) return tokenMeta.getAttribute('content');
|
|
|
+ const tokenInput = document.querySelector('input[name="_token"]');
|
|
|
+ if (tokenInput) return tokenInput.value;
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ function showTemporaryMessage(container, message, color) {
|
|
|
+ const existingMsg = container.querySelector('.temp-msg');
|
|
|
+ if (existingMsg) existingMsg.remove();
|
|
|
+
|
|
|
+ const msgDiv = document.createElement('div');
|
|
|
+ msgDiv.className = 'temp-msg';
|
|
|
+ msgDiv.style.cssText = `display: block; margin-top: 5px; font-size: 12px; color: ${color};`;
|
|
|
+ msgDiv.innerHTML = message;
|
|
|
+ container.appendChild(msgDiv);
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ if (msgDiv.parentNode) msgDiv.remove();
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+
|
|
|
+ function clearValidation() {
|
|
|
+ if (routeInput) {
|
|
|
+ routeInput.style.borderColor = '';
|
|
|
+ routeInput.style.backgroundColor = '';
|
|
|
+ }
|
|
|
+ if (validationResult) {
|
|
|
+ validationResult.innerHTML = '';
|
|
|
+ validationResult.style.display = 'none';
|
|
|
+ }
|
|
|
+ routeIsValid = false;
|
|
|
+ lastValidatedRoute = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ function showLoadingState() {
|
|
|
+ if (routeInput) {
|
|
|
+ routeInput.style.borderColor = '#ffc107';
|
|
|
+ routeInput.style.backgroundColor = '#fffbeb';
|
|
|
+ }
|
|
|
+ if (validationResult) {
|
|
|
+ validationResult.innerHTML = '<span style="color: #ffc107;">⏳ 正在验证路由...</span>';
|
|
|
+ validationResult.style.display = 'block';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function showSuccessState(message) {
|
|
|
+ if (routeInput) {
|
|
|
+ routeInput.style.borderColor = '#28a745';
|
|
|
+ routeInput.style.backgroundColor = '#f0fff4';
|
|
|
+ }
|
|
|
+ if (validationResult) {
|
|
|
+ validationResult.innerHTML = '<span style="color: #28a745;">✓ ' + message + '</span>';
|
|
|
+ validationResult.style.display = 'block';
|
|
|
+ }
|
|
|
+ setTimeout(() => {
|
|
|
+ if (routeInput) routeInput.style.backgroundColor = '';
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+
|
|
|
+ function showErrorState(message) {
|
|
|
+ if (routeInput) {
|
|
|
+ routeInput.style.borderColor = '#dc3545';
|
|
|
+ routeInput.style.backgroundColor = '#fff5f5';
|
|
|
+ }
|
|
|
+ if (validationResult) {
|
|
|
+ validationResult.innerHTML = '<span style="color: #dc3545;">✗ ' + message + '</span>';
|
|
|
+ validationResult.style.display = 'block';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function validateRoute(routeName) {
|
|
|
+ console.log('Validating route:', routeName);
|
|
|
+
|
|
|
+ if (!routeName) {
|
|
|
+ clearValidation();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ showLoadingState();
|
|
|
+
|
|
|
+ const csrfToken = getCsrfToken();
|
|
|
+ const validateUrl = '{{ route("admin.dynamicmenu.validate-route") }}';
|
|
|
+
|
|
|
+ fetch(validateUrl, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ 'X-CSRF-TOKEN': csrfToken,
|
|
|
+ 'Accept': 'application/json'
|
|
|
+ },
|
|
|
+ body: JSON.stringify({ route: routeName })
|
|
|
+ })
|
|
|
+ .then(response => response.json())
|
|
|
+ .then(data => {
|
|
|
+ console.log('Validation response:', data);
|
|
|
+ if (data.exists) {
|
|
|
+ routeIsValid = true;
|
|
|
+ showSuccessState(data.message);
|
|
|
+ } else {
|
|
|
+ routeIsValid = false;
|
|
|
+ let errorMsg = data.message || '路由不存在';
|
|
|
+ if (data.suggestions && data.suggestions.length > 0) {
|
|
|
+ errorMsg += '<br>建议使用:<br>';
|
|
|
+ data.suggestions.forEach(s => {
|
|
|
+ errorMsg += ` • ${s.name} (${s.uri})<br>`;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ showErrorState(errorMsg);
|
|
|
+ }
|
|
|
+ lastValidatedRoute = routeName;
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('Validation error:', error);
|
|
|
+ routeIsValid = false;
|
|
|
+ showErrorState('验证失败,请稍后重试');
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用事件委托
|
|
|
+ document.body.addEventListener('input', function(e) {
|
|
|
+ if (e.target.id === 'routeInput') {
|
|
|
+ console.log('Input event on routeInput:', e.target.value);
|
|
|
+ const routeValue = e.target.value.trim();
|
|
|
+
|
|
|
+ if (validationTimeout) clearTimeout(validationTimeout);
|
|
|
+
|
|
|
+ if (!routeValue) {
|
|
|
+ clearValidation();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ validationTimeout = setTimeout(() => {
|
|
|
+ validateRoute(routeValue);
|
|
|
+ }, 500);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.body.addEventListener('blur', function(e) {
|
|
|
+ if (e.target.id === 'routeInput') {
|
|
|
+ console.log('Blur event on routeInput:', e.target.value);
|
|
|
+ const routeValue = e.target.value.trim();
|
|
|
+ if (routeValue && routeValue !== lastValidatedRoute) {
|
|
|
+ validateRoute(routeValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 自动生成 key(仅当 key 为空时)
|
|
|
+ if (nameInput && keyInput) {
|
|
|
+ nameInput.addEventListener('blur', function() {
|
|
|
+ const name = this.value.trim();
|
|
|
+ const key = keyInput.value.trim();
|
|
|
+
|
|
|
+ if (key === '' && name !== '') {
|
|
|
+ let generatedKey = name.toLowerCase()
|
|
|
+ .replace(/\s+/g, '-')
|
|
|
+ .replace(/[^a-z0-9-]/g, '');
|
|
|
+
|
|
|
+ if (generatedKey) {
|
|
|
+ keyInput.value = 'settings.' + generatedKey;
|
|
|
+ keyInput.style.borderColor = '#28a745';
|
|
|
+ showTemporaryMessage(keyInput.parentElement, '✓ 已自动生成Key: settings.' + generatedKey, '#28a745');
|
|
|
+ setTimeout(() => {
|
|
|
+ keyInput.style.borderColor = '';
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Key 格式提示
|
|
|
+ if (keyInput) {
|
|
|
+ keyInput.addEventListener('focus', function() {
|
|
|
+ const currentVal = this.value;
|
|
|
+ if (currentVal && !currentVal.startsWith('settings.')) {
|
|
|
+ showTemporaryMessage(this.parentElement,
|
|
|
+ '⚠️ 警告:当前Key不以"settings."开头,菜单可能不会显示在Settings下',
|
|
|
+ '#dc3545');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 表单提交验证
|
|
|
+ if (menuForm) {
|
|
|
+ menuForm.addEventListener('submit', function(e) {
|
|
|
+ const routeValue = routeInput.value.trim();
|
|
|
+
|
|
|
+ if (routeValue) {
|
|
|
+ const isLoading = validationResult && validationResult.innerHTML.includes('正在验证');
|
|
|
+ const hasError = validationResult && validationResult.innerHTML.includes('✗');
|
|
|
+
|
|
|
+ if (isLoading) {
|
|
|
+ e.preventDefault();
|
|
|
+ alert('正在验证路由,请稍候...');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (hasError) {
|
|
|
+ e.preventDefault();
|
|
|
+ routeInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
+ routeInput.focus();
|
|
|
+ alert('路由不存在,请修正后再提交!\n\n提示:\n' +
|
|
|
+ '1. 检查路由名称是否正确\n' +
|
|
|
+ '2. 确保路由已在 routes/web.php 中定义\n' +
|
|
|
+ '3. 可以使用完整URL(如:https://example.com)跳过验证');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有初始值,验证一下
|
|
|
+ if (routeInput.value.trim()) {
|
|
|
+ setTimeout(() => {
|
|
|
+ validateRoute(routeInput.value.trim());
|
|
|
+ }, 500);
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('DynamicMenu Edit: Validation initialized successfully');
|
|
|
}
|
|
|
- });
|
|
|
- });*/
|
|
|
- </script>
|
|
|
+ })();
|
|
|
+ </script>
|
|
|
@endpush
|
|
|
</x-admin::layouts>
|