diff --git a/config/admin.php b/config/admin.php index d0471e3c0..149303263 100755 --- a/config/admin.php +++ b/config/admin.php @@ -112,8 +112,9 @@ |-------------------------------------------------------------------------- | | The version string appended to JS/CSS URLs as a cache-busting query - | parameter (e.g. `?v1.0.0`). Defaults to the framework version. Set to - | null/empty to disable the version query entirely. + | parameter (e.g. `?v1.0.0`). When null (env not set), falls back to the + | framework version. Set to an empty string ('') to disable the version + | query entirely. | */ 'assets_version' => env('ADMIN_ASSETS_VERSION'), diff --git a/resources/views/form/multipleselect.blade.php b/resources/views/form/multipleselect.blade.php index c6ed2e195..e75b413c9 100755 --- a/resources/views/form/multipleselect.blade.php +++ b/resources/views/form/multipleselect.blade.php @@ -23,7 +23,7 @@ @include('admin::form.select-script') diff --git a/src/Console/stubs/config.stub b/src/Console/stubs/config.stub index 974e6706c..8555a60b4 100644 --- a/src/Console/stubs/config.stub +++ b/src/Console/stubs/config.stub @@ -110,8 +110,9 @@ return [ |-------------------------------------------------------------------------- | | The version string appended to JS/CSS URLs as a cache-busting query - | parameter (e.g. `?v1.0.0`). Defaults to the framework version. Set to - | null/empty to disable the version query entirely. + | parameter (e.g. `?v1.0.0`). When null (env not set), falls back to the + | framework version. Set to an empty string ('') to disable the version + | query entirely. | */ 'assets_version' => env('ADMIN_ASSETS_VERSION'), diff --git a/src/Layout/Asset.php b/src/Layout/Asset.php index fa24e97e5..2690467f7 100644 --- a/src/Layout/Asset.php +++ b/src/Layout/Asset.php @@ -689,9 +689,10 @@ public function withVersionQuery($url) $url .= '?'; } - $assetsVersion = config('admin.assets_version', Admin::VERSION); + // 键存在但为 null(env 未设置)时回退框架版本;显式置空才禁用版本参数 + $assetsVersion = config('admin.assets_version') ?? Admin::VERSION; - if (! $assetsVersion) { + if ($assetsVersion === '' || $assetsVersion === false) { return rtrim($url, '?'); } diff --git a/src/Support/SessionMessage.php b/src/Support/SessionMessage.php index e6a3f29e0..e972e7257 100644 --- a/src/Support/SessionMessage.php +++ b/src/Support/SessionMessage.php @@ -2,12 +2,17 @@ namespace Dcat\Admin\Support; +use Illuminate\Contracts\Support\MessageBag; + /** * Session 消息对象(兼容 Laravel 10-13,兼容 PHP session.serialization = json/php). * * 解决两类问题: * 1. Laravel 13 中 MessageBag 通过 redirect()->with() 序列化后变为数组的问题 * 2. session.serialization = json 时,protected 属性不会被 json_encode 包含的问题 + * + * 同时向后兼容旧契约:外部代码直接 flash 的 MessageBag(实例或其序列化数组) + * 仍可被 tryFrom() 识别,不会被静默丢弃。 */ final class SessionMessage implements \JsonSerializable { @@ -67,9 +72,11 @@ public function jsonSerialize(): mixed /** * 从 session 中读取的值尝试构造实例. * - * 兼容两种情况: + * 兼容以下情况: * - PHP 序列化(session.serialization = php):值已经是 SessionMessage 对象 * - JSON 序列化(session.serialization = json):值是带类型标识的数组 + * - 旧契约:外部代码直接 flash 的 MessageBag 实例,或其序列化后的数组 + * (形如 ['title' => ['x'], 'message' => ['y']]) */ public static function tryFrom(mixed $value): ?self { @@ -77,10 +84,15 @@ public static function tryFrom(mixed $value): ?self return $value; } - if ( - is_array($value) - && ($value[self::JSON_CLASS_KEY] ?? null) === self::JSON_CLASS_VALUE - ) { + if ($value instanceof MessageBag) { + $value = $value->getMessages(); + } + + if (! is_array($value)) { + return null; + } + + if (($value[self::JSON_CLASS_KEY] ?? null) === self::JSON_CLASS_VALUE) { return new self( is_string($value['title'] ?? null) ? $value['title'] : '', is_string($value['message'] ?? null) ? $value['message'] : '', @@ -88,6 +100,29 @@ public static function tryFrom(mixed $value): ?self ); } - return null; + return self::tryFromMessageBagArray($value); + } + + /** + * 尝试按 MessageBag 序列化数组的形态构造实例(title/message 值为字符串数组). + */ + private static function tryFromMessageBagArray(array $value): ?self + { + if ( + (! isset($value['title']) && ! isset($value['message'])) + || ! is_array($value['title'] ?? []) + || ! is_array($value['message'] ?? []) + ) { + return null; + } + + $title = $value['title'][0] ?? ''; + $message = $value['message'][0] ?? ''; + + if (! is_string($title) || ! is_string($message)) { + return null; + } + + return new self($title, $message); } } diff --git a/src/Traits/HasModelOptions.php b/src/Traits/HasModelOptions.php index 95959b948..c7f297f4e 100644 --- a/src/Traits/HasModelOptions.php +++ b/src/Traits/HasModelOptions.php @@ -12,18 +12,20 @@ trait HasModelOptions * Load options from current selected resource(s). * * @param string $model - * @param string $idField + * @param string|null $idField 默认取模型主键 * @param string $textField * @return $this */ - public function model($model, string $idField = 'id', string $textField = 'name') + public function model($model, ?string $idField = null, string $textField = 'name') { if (! class_exists($model) - || ! in_array(Model::class, class_parents($model)) + || ! in_array(Model::class, class_parents($model), true) ) { throw new RuntimeException("[$model] must be a valid model class"); } + $idField ??= (new $model())->getKeyName(); + $this->options = function ($value) use ($model, $idField, $textField) { if (empty($value)) { return []; diff --git a/src/Widgets/Dropdown.php b/src/Widgets/Dropdown.php index ecba44d46..f51761998 100644 --- a/src/Widgets/Dropdown.php +++ b/src/Widgets/Dropdown.php @@ -219,7 +219,7 @@ protected function renderOption($k, $v) $v = $builder->call($this, $v, $k); } - $v = str_starts_with($v, '') ? $v : "$v"; + $v = str_contains($v, '') ? $v : "$v"; $v = ""; if ($this->divider) { diff --git a/tests/Unit/SessionMessageTest.php b/tests/Unit/SessionMessageTest.php index 066a04aac..c53549386 100644 --- a/tests/Unit/SessionMessageTest.php +++ b/tests/Unit/SessionMessageTest.php @@ -189,4 +189,54 @@ public function test_try_from_ignores_non_array_options(): void $this->assertSame('hello', $restored->getMessage()); $this->assertSame([], $restored->getOptions()); } -} \ No newline at end of file + + // ----------------------------------------------------------------------- + // tryFrom — 旧契约 MessageBag 兼容 + // ----------------------------------------------------------------------- + + public function test_try_from_accepts_legacy_message_bag_instance(): void + { + $bag = new \Illuminate\Support\MessageBag([ + 'title' => '操作成功', + 'message' => '记录已保存', + ]); + + $restored = SessionMessage::tryFrom($bag); + + $this->assertNotNull($restored); + $this->assertSame('操作成功', $restored->getTitle()); + $this->assertSame('记录已保存', $restored->getMessage()); + } + + public function test_try_from_accepts_legacy_message_bag_serialized_array(): void + { + $bag = new \Illuminate\Support\MessageBag([ + 'title' => '操作成功', + 'message' => '记录已保存', + ]); + $array = json_decode(json_encode($bag), true); + + $restored = SessionMessage::tryFrom($array); + + $this->assertNotNull($restored); + $this->assertSame('操作成功', $restored->getTitle()); + $this->assertSame('记录已保存', $restored->getMessage()); + } + + public function test_try_from_accepts_message_bag_with_only_title(): void + { + $restored = SessionMessage::tryFrom(['title' => ['仅标题']]); + + $this->assertNotNull($restored); + $this->assertSame('仅标题', $restored->getTitle()); + $this->assertSame('', $restored->getMessage()); + } + + public function test_try_from_rejects_message_bag_shaped_array_with_non_string_values(): void + { + $this->assertNull(SessionMessage::tryFrom([ + 'title' => [['nested']], + 'message' => ['hi'], + ])); + } +}