fix(web): 本地优先保存与 design 页体验修复

- 保存先写本地缓存,登录后可选云端同步;退出登录不再因文件操作跳转登录

- 修复 Layui 遮罩残留、design2 保存后线条、toast 被 finally 提前关闭

- 保存过程恢复 loading spinner;成功提示 2.5 秒

- 云端 payload 瘦身与体积超限提示;后端 schema 迁移与 FileService 容错

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
24kycj
2026-06-24 19:24:34 +08:00
parent 4d19d7e814
commit 5fb269cc0a
36 changed files with 894 additions and 358 deletions
+53
View File
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Soon\Api\Core;
use PDOException;
use Throwable;
/**
* API 未捕获异常 → JSON 响应(避免空白 500)。
*/
final class ErrorHandler
{
public static function register(): void
{
set_exception_handler([self::class, 'handleException']);
}
public static function handleException(Throwable $e): void
{
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
if (!str_starts_with($path, '/api/')) {
http_response_code(500);
exit;
}
$debug = (bool)Config::get('app.debug', false);
$code = 'server_error';
$msg = '服务器错误,请稍后重试';
$status = 500;
if ($e instanceof PDOException) {
$msg = '数据库操作失败';
$sqlState = (string)$e->getCode();
$detail = $e->getMessage();
if (str_contains($detail, 'max_allowed_packet') || str_contains($detail, 'Packet too large')) {
$code = 'payload_too_large';
$msg = '文件过大,超出服务器数据库限制';
$status = 413;
} elseif (str_contains($detail, 'Unknown column') || str_contains($detail, "doesn't exist")) {
$msg = '数据库结构不完整,请导入 database/schema.sql 或执行 php scripts/migrate-schema.php';
} elseif ($sqlState === '22001' || str_contains($detail, 'Data too long')) {
$code = 'payload_too_large';
$msg = '文件内容过长,无法保存';
$status = 413;
}
}
if ($debug) {
$msg .= ' (' . $e->getMessage() . ')';
}
Json::fail($code, $msg, $status);
}
}