Web 端本地缓存、首页与会员弹窗体验优化
新增 IndexedDB 与最近文件 L1 缓存、云保存/打开链路;首页支持下载与清空本地缓存;修复清空 storage 后语言初始化报错;优化 design2 背景加载与侧栏标签样式;完善会员激活/支付弹层样式;后端补充文件缩略图字段与 thumb 接口。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -55,7 +55,8 @@ final class FileService
|
||||
public static function list(int $userId, int $limit, int $offset): array
|
||||
{
|
||||
$stmt = Db::pdo()->prepare(
|
||||
'SELECT id, name, size, version, updated_at, created_at '
|
||||
'SELECT id, name, size, version, updated_at, created_at, '
|
||||
. '(CASE WHEN thumb IS NOT NULL AND thumb <> "" THEN 1 ELSE 0 END) AS has_thumb '
|
||||
. 'FROM soon_files WHERE user_id = :u AND deleted_at IS NULL '
|
||||
. 'ORDER BY updated_at DESC LIMIT :lim OFFSET :off'
|
||||
);
|
||||
@@ -63,7 +64,12 @@ final class FileService
|
||||
$stmt->bindValue('lim', $limit, \PDO::PARAM_INT);
|
||||
$stmt->bindValue('off', $offset, \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll();
|
||||
$rows = $stmt->fetchAll();
|
||||
foreach ($rows as &$row) {
|
||||
$row['has_thumb'] = (int)($row['has_thumb'] ?? 0) === 1;
|
||||
}
|
||||
unset($row);
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public static function create(int $userId, string $name, string $json): array
|
||||
@@ -79,11 +85,15 @@ final class FileService
|
||||
Json::fail('quota_exceeded', '存储空间已满,请清理文件或续订', 413);
|
||||
}
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$thumb = TemplateService::thumbFromSoonJson($json);
|
||||
$stmt = Db::pdo()->prepare(
|
||||
'INSERT INTO soon_files (user_id, name, json, size, version, created_at, updated_at) '
|
||||
. 'VALUES (:u, :n, :j, :s, 1, :created_at, :updated_at)'
|
||||
'INSERT INTO soon_files (user_id, name, json, size, version, thumb, created_at, updated_at) '
|
||||
. 'VALUES (:u, :n, :j, :s, 1, :th, :created_at, :updated_at)'
|
||||
);
|
||||
$stmt->execute(['u' => $userId, 'n' => $name, 'j' => $json, 's' => $size, 'created_at' => $now, 'updated_at' => $now]);
|
||||
$stmt->execute([
|
||||
'u' => $userId, 'n' => $name, 'j' => $json, 's' => $size, 'th' => $thumb !== '' ? $thumb : null,
|
||||
'created_at' => $now, 'updated_at' => $now,
|
||||
]);
|
||||
$id = (int)Db::pdo()->lastInsertId();
|
||||
return ['id' => $id, 'name' => $name, 'size' => $size, 'version' => 1, 'updated_at' => $now];
|
||||
}
|
||||
@@ -111,12 +121,14 @@ final class FileService
|
||||
}
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$newVersion = (int)$row['version'] + 1;
|
||||
$thumb = TemplateService::thumbFromSoonJson($json);
|
||||
$upd = $pdo->prepare(
|
||||
'UPDATE soon_files SET name = :n, json = :j, size = :s, version = :v, updated_at = :ts '
|
||||
'UPDATE soon_files SET name = :n, json = :j, size = :s, version = :v, thumb = :th, updated_at = :ts '
|
||||
. 'WHERE id = :id AND version = :cv'
|
||||
);
|
||||
$upd->execute([
|
||||
'n' => $name, 'j' => $json, 's' => $newSize, 'v' => $newVersion,
|
||||
'th' => $thumb !== '' ? $thumb : null,
|
||||
'ts' => $now, 'id' => $id, 'cv' => (int)$row['version'],
|
||||
]);
|
||||
if ($upd->rowCount() === 0) {
|
||||
@@ -152,4 +164,22 @@ final class FileService
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
public static function outputThumb(int $userId, int $id): void
|
||||
{
|
||||
$row = self::fetch($userId, $id);
|
||||
$thumb = trim((string)($row['thumb'] ?? ''));
|
||||
if ($thumb === '' || !str_starts_with($thumb, 'data:image/')) {
|
||||
$thumb = TemplateService::thumbFromSoonJson((string)$row['json']);
|
||||
if ($thumb !== '') {
|
||||
Db::pdo()->prepare('UPDATE soon_files SET thumb = :th WHERE id = :id AND user_id = :u')
|
||||
->execute(['th' => $thumb, 'id' => $id, 'u' => $userId]);
|
||||
}
|
||||
}
|
||||
if ($thumb === '') {
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
TemplateService::outputThumbDataUrl($thumb, (string)$row['updated_at']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,12 +372,12 @@ final class MembershipService
|
||||
$fileStmt->execute(['u' => $userId]);
|
||||
$row = $fileStmt->fetch() ?: ['files_count' => 0, 'storage_bytes' => 0];
|
||||
$bytes = (int)$row['storage_bytes'];
|
||||
$usedMb = $bytes > 0 ? round($bytes / 1024 / 1024, 2) : 0;
|
||||
$usedMb = $bytes > 0 ? round($bytes / 1024 / 1024, 2) : 0.0;
|
||||
return [
|
||||
'files_count' => (int)$row['files_count'],
|
||||
'storage_bytes' => $bytes,
|
||||
'used_mb' => $usedMb,
|
||||
'used_display' => self::formatQuota(max(1, $usedMb)) !== '0 MB' ? self::formatBytes($bytes) : '0 MB',
|
||||
'used_display' => $bytes > 0 ? self::formatBytes($bytes) : '0 MB',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -434,12 +434,22 @@ final class TemplateService
|
||||
}
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Content-Disposition: inline; filename="' . str_replace('"', '', $name) . '.soon"');
|
||||
header('Cache-Control: public, max-age=60, must-revalidate');
|
||||
header('Cache-Control: public, max-age=86400, must-revalidate');
|
||||
header('ETag: "' . md5((string)$row['updated_at'] . ':' . (int)$row['file_size']) . '"');
|
||||
readfile($path);
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function thumbFromSoonJson(string $json): string
|
||||
{
|
||||
return self::parseSoonMeta($json)['thumb'];
|
||||
}
|
||||
|
||||
public static function outputThumbDataUrl(string $thumb, string $rev): void
|
||||
{
|
||||
self::emitThumbBinary($thumb, $rev);
|
||||
}
|
||||
|
||||
private static function writeFile(int $id, string $json): void
|
||||
{
|
||||
$path = self::storageDir() . DIRECTORY_SEPARATOR . $id . '.soon';
|
||||
|
||||
Reference in New Issue
Block a user