Web 端本地缓存、首页与会员弹窗体验优化
新增 IndexedDB 与最近文件 L1 缓存、云保存/打开链路;首页支持下载与清空本地缓存;修复清空 storage 后语言初始化报错;优化 design2 背景加载与侧栏标签样式;完善会员激活/支付弹层样式;后端补充文件缩略图字段与 thumb 接口。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -46,6 +46,7 @@ Router::get('/api/v1/auth/me', [AuthController::class, 'me']);
|
|||||||
|
|
||||||
Router::get('/api/v1/files', [FileController::class, 'index']);
|
Router::get('/api/v1/files', [FileController::class, 'index']);
|
||||||
Router::post('/api/v1/files', [FileController::class, 'create']);
|
Router::post('/api/v1/files', [FileController::class, 'create']);
|
||||||
|
Router::get('/api/v1/files/{id}/thumb', [FileController::class, 'thumb']);
|
||||||
Router::get('/api/v1/files/{id}', [FileController::class, 'show']);
|
Router::get('/api/v1/files/{id}', [FileController::class, 'show']);
|
||||||
Router::put('/api/v1/files/{id}', [FileController::class, 'update']);
|
Router::put('/api/v1/files/{id}', [FileController::class, 'update']);
|
||||||
Router::delete('/api/v1/files/{id}', [FileController::class, 'delete']);
|
Router::delete('/api/v1/files/{id}', [FileController::class, 'delete']);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ CREATE TABLE IF NOT EXISTS `soon_files` (
|
|||||||
`json` LONGTEXT NOT NULL,
|
`json` LONGTEXT NOT NULL,
|
||||||
`size` INT UNSIGNED NOT NULL DEFAULT 0,
|
`size` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
`version` INT UNSIGNED NOT NULL DEFAULT 1,
|
`version` INT UNSIGNED NOT NULL DEFAULT 1,
|
||||||
|
`thumb` MEDIUMTEXT DEFAULT NULL,
|
||||||
`created_at` DATETIME NOT NULL,
|
`created_at` DATETIME NOT NULL,
|
||||||
`updated_at` DATETIME NOT NULL,
|
`updated_at` DATETIME NOT NULL,
|
||||||
`deleted_at` DATETIME DEFAULT NULL,
|
`deleted_at` DATETIME DEFAULT NULL,
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ final class FileController
|
|||||||
Json::ok(['id' => $id]);
|
Json::ok(['id' => $id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function thumb(int $id): void
|
||||||
|
{
|
||||||
|
$u = Auth::require();
|
||||||
|
FileService::outputThumb($u['id'], $id);
|
||||||
|
}
|
||||||
|
|
||||||
public function download(int $id): void
|
public function download(int $id): void
|
||||||
{
|
{
|
||||||
$u = Auth::require();
|
$u = Auth::require();
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ final class FileService
|
|||||||
public static function list(int $userId, int $limit, int $offset): array
|
public static function list(int $userId, int $limit, int $offset): array
|
||||||
{
|
{
|
||||||
$stmt = Db::pdo()->prepare(
|
$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 '
|
. 'FROM soon_files WHERE user_id = :u AND deleted_at IS NULL '
|
||||||
. 'ORDER BY updated_at DESC LIMIT :lim OFFSET :off'
|
. '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('lim', $limit, \PDO::PARAM_INT);
|
||||||
$stmt->bindValue('off', $offset, \PDO::PARAM_INT);
|
$stmt->bindValue('off', $offset, \PDO::PARAM_INT);
|
||||||
$stmt->execute();
|
$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
|
public static function create(int $userId, string $name, string $json): array
|
||||||
@@ -79,11 +85,15 @@ final class FileService
|
|||||||
Json::fail('quota_exceeded', '存储空间已满,请清理文件或续订', 413);
|
Json::fail('quota_exceeded', '存储空间已满,请清理文件或续订', 413);
|
||||||
}
|
}
|
||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
|
$thumb = TemplateService::thumbFromSoonJson($json);
|
||||||
$stmt = Db::pdo()->prepare(
|
$stmt = Db::pdo()->prepare(
|
||||||
'INSERT INTO soon_files (user_id, name, json, size, version, 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, :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();
|
$id = (int)Db::pdo()->lastInsertId();
|
||||||
return ['id' => $id, 'name' => $name, 'size' => $size, 'version' => 1, 'updated_at' => $now];
|
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');
|
$now = date('Y-m-d H:i:s');
|
||||||
$newVersion = (int)$row['version'] + 1;
|
$newVersion = (int)$row['version'] + 1;
|
||||||
|
$thumb = TemplateService::thumbFromSoonJson($json);
|
||||||
$upd = $pdo->prepare(
|
$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'
|
. 'WHERE id = :id AND version = :cv'
|
||||||
);
|
);
|
||||||
$upd->execute([
|
$upd->execute([
|
||||||
'n' => $name, 'j' => $json, 's' => $newSize, 'v' => $newVersion,
|
'n' => $name, 'j' => $json, 's' => $newSize, 'v' => $newVersion,
|
||||||
|
'th' => $thumb !== '' ? $thumb : null,
|
||||||
'ts' => $now, 'id' => $id, 'cv' => (int)$row['version'],
|
'ts' => $now, 'id' => $id, 'cv' => (int)$row['version'],
|
||||||
]);
|
]);
|
||||||
if ($upd->rowCount() === 0) {
|
if ($upd->rowCount() === 0) {
|
||||||
@@ -152,4 +164,22 @@ final class FileService
|
|||||||
}
|
}
|
||||||
return $row;
|
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]);
|
$fileStmt->execute(['u' => $userId]);
|
||||||
$row = $fileStmt->fetch() ?: ['files_count' => 0, 'storage_bytes' => 0];
|
$row = $fileStmt->fetch() ?: ['files_count' => 0, 'storage_bytes' => 0];
|
||||||
$bytes = (int)$row['storage_bytes'];
|
$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 [
|
return [
|
||||||
'files_count' => (int)$row['files_count'],
|
'files_count' => (int)$row['files_count'],
|
||||||
'storage_bytes' => $bytes,
|
'storage_bytes' => $bytes,
|
||||||
'used_mb' => $usedMb,
|
'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-Type: application/json; charset=utf-8');
|
||||||
header('Content-Disposition: inline; filename="' . str_replace('"', '', $name) . '.soon"');
|
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']) . '"');
|
header('ETag: "' . md5((string)$row['updated_at'] . ':' . (int)$row['file_size']) . '"');
|
||||||
readfile($path);
|
readfile($path);
|
||||||
exit;
|
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
|
private static function writeFile(int $id, string $json): void
|
||||||
{
|
{
|
||||||
$path = self::storageDir() . DIRECTORY_SEPARATOR . $id . '.soon';
|
$path = self::storageDir() . DIRECTORY_SEPARATOR . $id . '.soon';
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ if (!columnExists($pdo, 'users', 'admin_level')) {
|
|||||||
$alters[] = "ALTER TABLE users ADD COLUMN admin_level ENUM('full','ops') DEFAULT NULL AFTER role";
|
$alters[] = "ALTER TABLE users ADD COLUMN admin_level ENUM('full','ops') DEFAULT NULL AFTER role";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!columnExists($pdo, 'soon_files', 'thumb')) {
|
||||||
|
$alters[] = 'ALTER TABLE soon_files ADD COLUMN thumb MEDIUMTEXT DEFAULT NULL AFTER version';
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($alters as $sql) {
|
foreach ($alters as $sql) {
|
||||||
$pdo->exec($sql);
|
$pdo->exec($sql);
|
||||||
fwrite(STDOUT, "migrate-dev-schema: {$sql}" . PHP_EOL);
|
fwrite(STDOUT, "migrate-dev-schema: {$sql}" . PHP_EOL);
|
||||||
|
|||||||
@@ -168,7 +168,8 @@ GET /api/v1/pay/orders?page=1&size=8
|
|||||||
| `GET /api/v1/templates` | 首页模板列表(仅 `id/name/type/updated_at`;缩略图按需拉取) |
|
| `GET /api/v1/templates` | 首页模板列表(仅 `id/name/type/updated_at`;缩略图按需拉取) |
|
||||||
| `GET /api/v1/templates/{id}` | 模板 JSON 数据(门户标准读取端点,见下) |
|
| `GET /api/v1/templates/{id}` | 模板 JSON 数据(门户标准读取端点,见下) |
|
||||||
| `GET /api/v1/templates/{id}/thumb` | 模板缩略图(从 `.soon` 内 `frontDisplayPic` 提取) |
|
| `GET /api/v1/templates/{id}/thumb` | 模板缩略图(从 `.soon` 内 `frontDisplayPic` 提取) |
|
||||||
| `GET /api/v1/templates/{id}/file` | **legacy**:裸 `.soon` 流;门户禁止调用,仅供兼容 |
|
| `GET /api/v1/templates/{id}/file` | 裸 `.soon` 流(`Cache-Control: max-age=86400`);门户读缓存首选 |
|
||||||
|
| `GET /api/v1/files/{id}/thumb` | 云文件缩略图(需登录;从 `.soon` 内 `frontDisplayPic` 提取) |
|
||||||
| `GET /api/v1/soon-models` | 兼容别名,同 `GET /api/v1/templates` |
|
| `GET /api/v1/soon-models` | 兼容别名,同 `GET /api/v1/templates` |
|
||||||
|
|
||||||
#### `GET /api/v1/templates/{id}`(门户读取模板数据)
|
#### `GET /api/v1/templates/{id}`(门户读取模板数据)
|
||||||
@@ -196,10 +197,11 @@ GET /api/v1/pay/orders?page=1&size=8
|
|||||||
| 前缀 | 含义 | readJsonFile | 保存 writeFile |
|
| 前缀 | 含义 | readJsonFile | 保存 writeFile |
|
||||||
|------|------|--------------|----------------|
|
|------|------|--------------|----------------|
|
||||||
| `soondesign_file:{id}:v{ver}` | 云端已登记文件 | `GET /files/{id}` | `PUT /files/{id}` |
|
| `soondesign_file:{id}:v{ver}` | 云端已登记文件 | `GET /files/{id}` | `PUT /files/{id}` |
|
||||||
| `soondesign_template:{id}` | 云端模板(只读源) | `GET /templates/{id}` | `POST /files`(首次保存新建) |
|
| `soondesign_template:{id}` | 云端模板(只读源) | `GET /templates/{id}/file` | `POST /files`(首次保存新建) |
|
||||||
| `soondesign_session:...` | 本地/临时会话 | sessionStorage | 已登录 `POST /files`;未登录写 session |
|
| `soondesign_session:...` | 本地/临时会话 | sessionStorage | 已登录 `POST /files`;未登录写 session |
|
||||||
|
|
||||||
- **保存/打开**走 JSON API;**仅**首页「下载」走 `GET /files/{id}/download` 落盘 `.soon`
|
- **保存/打开**走 JSON API;**仅**首页「下载」走 `GET /files/{id}/download` 落盘 `.soon`
|
||||||
|
- 门户**读缓存**与最近文件本地优先策略见 [`WEB-LOCAL-CACHE.md`](WEB-LOCAL-CACHE.md);模板首次加载推荐 `GET /templates/{id}/file`(流式 `.soon`,避免 `GET /templates/{id}` 二次包装)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -236,3 +238,4 @@ GET /api/v1/pay/orders?page=1&size=8
|
|||||||
|------|------|
|
|------|------|
|
||||||
| 2026-06-08 | 初版:统一 page/size/total;files 兼容 limit/offset;pay/orders 分页 |
|
| 2026-06-08 | 初版:统一 page/size/total;files 兼容 limit/offset;pay/orders 分页 |
|
||||||
| 2026-06-08 | 新增 GET /templates/{id} JSON 契约;Web 虚拟 key 前缀;/file 标 legacy |
|
| 2026-06-08 | 新增 GET /templates/{id} JSON 契约;Web 虚拟 key 前缀;/file 标 legacy |
|
||||||
|
| 2026-06-08 | 新增 GET /files/{id}/thumb;files list 增 `has_thumb`;模板 /file 长缓存 |
|
||||||
|
|||||||
@@ -0,0 +1,400 @@
|
|||||||
|
# Web 端本地缓存与最近文件优化(开发计划 · 细化版)
|
||||||
|
|
||||||
|
> 状态:P0~P7 已实施。与 [API-PAGINATION.md](API-PAGINATION.md)「Web 虚拟 key」交叉引用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 背景与约束
|
||||||
|
|
||||||
|
### 0.1 问题(已核实代码)
|
||||||
|
|
||||||
|
| 现象 | 根因 | 代码位置 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `GET /templates/{id}` 20~30s | PHP 二次 `json_encode` + 大响应 + 客户端双次 parse | `TemplateService::fetchPublicJson`;`web.js` ~268 行 |
|
||||||
|
| 重复打开仍打源站 | 无 IndexedDB | 全库无 `indexedDB` |
|
||||||
|
| 首页最近文件慢 | 每卡 `readJsonFile` 全量 | `index.js` ~381 行 |
|
||||||
|
| 保存后不显示 | Web `onCloudWriteDone` 未写 history | `design1/output.js` ~1266;仅 fs 分支 `saveHistory` |
|
||||||
|
| session 易超配额 | 大 JSON 写 session/localStorage | `cloud-files.js` `soonPutSoonSession` |
|
||||||
|
|
||||||
|
### 0.2 硬约束
|
||||||
|
|
||||||
|
- **不改** `.soon` 格式;**保留** base64。
|
||||||
|
- **不上 CDN**;靠本机 IDB + 传输路径精简 + L1 元数据。
|
||||||
|
- 保存仍 `POST/PUT /files` 传完整 `json`;**仅读**本地优先。
|
||||||
|
- 配置:`frontend-web/config/local.js`、`backend-web/config/local.php`。
|
||||||
|
|
||||||
|
### 0.3 三层架构
|
||||||
|
|
||||||
|
| 层 | 存储 | 内容 | 体积 |
|
||||||
|
|----|------|------|------|
|
||||||
|
| L1 | `localStorage` `soondesign_recent` | key/name/type/kind/fileId/thumbRef/time | <50KB |
|
||||||
|
| L2 | IndexedDB `soondesign_local` | 完整 `.soon` 对象 + thumbs | ≤20 条 blob |
|
||||||
|
| L3 | HTTP API | miss / 保存 / 可选元数据对账 | 按需 |
|
||||||
|
|
||||||
|
### 0.4 脚本加载顺序(三页统一)
|
||||||
|
|
||||||
|
`index.web.html` / `design1.web.html` / `design2.web.html`:
|
||||||
|
|
||||||
|
```
|
||||||
|
config/local.js → auth → cloud-files.js → soon-local-store.js → soon-recent.js → web.js → 页面逻辑
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 数据契约
|
||||||
|
|
||||||
|
### 1.1 L1:`soondesign_recent`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"key": "soondesign_file:12:v3",
|
||||||
|
"name": "我的卡片.soon",
|
||||||
|
"type": 1,
|
||||||
|
"kind": "cloud",
|
||||||
|
"fileId": 12,
|
||||||
|
"thumbRef": "soondesign_file:12:v3",
|
||||||
|
"time": "2026-06-08 14:30:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `thumbRef`:与 L2 `thumbs` 的 cacheKey 相同(非独立字符串前缀)。
|
||||||
|
- 上限 **20**;`soonRecentUpsert` 去重后 unshift,截断尾部。
|
||||||
|
- **首页以 L1 为展示主数据源**;`GET /files` 仅后台补 `name`/`version`,不默认注入未打开过的云端文件。
|
||||||
|
|
||||||
|
### 1.2 L2:IndexedDB `soondesign_local` v1
|
||||||
|
|
||||||
|
| Store | keyPath | value |
|
||||||
|
|-------|---------|--------|
|
||||||
|
| `blobs` | `cacheKey` | `{ json, name, type, source, updatedAt, savedAt, bytes }` |
|
||||||
|
| `thumbs` | `cacheKey` | `{ dataUrl, savedAt }` |
|
||||||
|
|
||||||
|
**cacheKey**
|
||||||
|
|
||||||
|
| 来源 | cacheKey | 失效 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 云文件 | `soondesign_file:{id}:v{ver}` | PUT 后 version+1 → 新 key;旧 blob 可 `soonLocalRemove` |
|
||||||
|
| 模板 | `soondesign_template:{id}` | 见 §1.4 |
|
||||||
|
| 会话 | `soondesign_session:...` | 迁入云 key 后 `soonLocalRenameKey` |
|
||||||
|
|
||||||
|
**LRU**:`soonLocalEvictLRU(20)` 在每次 `put` 后执行;仅删 blob+thumb,**不删 L1**。
|
||||||
|
|
||||||
|
### 1.3 L3:读取端点
|
||||||
|
|
||||||
|
| 场景 | 端点 | 禁止 |
|
||||||
|
|------|------|------|
|
||||||
|
| 模板首次 | `GET /api/v1/templates/{id}/file` | 门户默认不用 `GET /templates/{id}` 包装 |
|
||||||
|
| 云文件 miss | `GET /api/v1/files/{id}` | — |
|
||||||
|
| thumb 兜底 | `GET /templates/{id}/thumb`;P6 `GET /files/{id}/thumb` | 禁止为 thumb 拉全量 json |
|
||||||
|
| 保存 | `POST`/`PUT /files` | 不变 |
|
||||||
|
|
||||||
|
### 1.4 模板缓存失效策略(细化)
|
||||||
|
|
||||||
|
1. 首页点模板前,`index.js` `openTemplateItem` 写入 `window._soonTemplateMeta = { id, name, type, updated_at }`(列表已有 `updated_at`)。
|
||||||
|
2. IDB `blobs` 的 `meta.updatedAt` 存该 `updated_at`。
|
||||||
|
3. `readJsonFile` IDB hit 时:若 `_soonTemplateMeta.updated_at` 存在且与 blob.meta.updatedAt 不一致 → 删 blob,`miss` 走网络。
|
||||||
|
4. 网络:`/templates/{id}/file` + parse;`soonLocalPut` 带最新 `updatedAt`。
|
||||||
|
5. 未经过首页的深链打开:无 meta 比较,信任 IDB(可接受;刷新列表后校正)。
|
||||||
|
|
||||||
|
### 1.5 共享工具 `soonExtractThumbFromJson(json)`
|
||||||
|
|
||||||
|
- 位置:`soon-local-store.js` 或 `soon-recent.js`。
|
||||||
|
- 取 `json.frontDisplayPic`;须 `data:image/` 开头。
|
||||||
|
- 超过 **102400** 字节则返回 `''`(不写 thumbs,首页用 type 默认图)。
|
||||||
|
- 打开/保存/导入成功后统一调用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 核心流程
|
||||||
|
|
||||||
|
### 2.1 打开(readJsonFile)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant UI as design_page
|
||||||
|
participant IDB as L2_IDB
|
||||||
|
participant L1 as L1_recent
|
||||||
|
participant API as L3_server
|
||||||
|
|
||||||
|
UI->>IDB: soonLocalGet(cacheKey)
|
||||||
|
alt hit_and_valid
|
||||||
|
IDB-->>UI: json
|
||||||
|
else miss_or_stale
|
||||||
|
UI->>API: GET file_or_files
|
||||||
|
API-->>UI: soon_bytes
|
||||||
|
UI->>IDB: soonLocalPut + PutThumb
|
||||||
|
UI->>L1: soonRecentUpsert
|
||||||
|
end
|
||||||
|
UI->>UI: openFile_loadFromJSON
|
||||||
|
```
|
||||||
|
|
||||||
|
**补充接入点**(P3):`doOpenWithJson` 完成画布加载后 `soonRecentUpsert`(模板/云文件/会话均记录,kind 由 key 推断)。
|
||||||
|
|
||||||
|
### 2.2 保存(writeFile + onCloudWriteDone)
|
||||||
|
|
||||||
|
**模板首次保存**:`soondesign_template:*` 或空 key 时弹窗,默认文件名 `design` + `yyMMddHHmmss` + `.soon`(如 `design260610103010.soon`);保存成功后 L1 用 cloud/local 条目替换原 template 项(`soonRecentOnCloudSave` + `prevOpenKey`)。
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant UI as save
|
||||||
|
participant IDB as L2
|
||||||
|
participant L1 as L1
|
||||||
|
participant API as server
|
||||||
|
|
||||||
|
UI->>IDB: soonLocalPut当前json
|
||||||
|
UI->>L1: soonRecentUpsert
|
||||||
|
alt cloud_key
|
||||||
|
UI->>API: PUT
|
||||||
|
API-->>UI: new_version
|
||||||
|
UI->>IDB: RenameKey_or_new_put
|
||||||
|
UI->>L1: upsert新key
|
||||||
|
else new_or_template
|
||||||
|
UI->>API: POST
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 首页最近文件
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Index as index
|
||||||
|
participant L1 as L1
|
||||||
|
participant IDB as thumbs
|
||||||
|
participant API as metadata_optional
|
||||||
|
|
||||||
|
Index->>L1: soonRecentList
|
||||||
|
loop each_card
|
||||||
|
Index->>IDB: soonLocalGetThumb(key)
|
||||||
|
alt no_thumb
|
||||||
|
Index->>Index: soonAsset默认图或thumb_URL
|
||||||
|
end
|
||||||
|
end
|
||||||
|
opt logged_in
|
||||||
|
Index->>API: listCloudFiles元数据
|
||||||
|
Index->>L1: 仅补name/version不增项
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 删除卡片
|
||||||
|
|
||||||
|
| 操作 | L1 | L2 | 服务器 |
|
||||||
|
|------|----|----|--------|
|
||||||
|
| 删本地项 | `soonRecentRemove` | `soonLocalRemove` | — |
|
||||||
|
| 删云文件 | `soonRecentRemove` | `soonLocalRemove` | `DELETE /files/{id}` |
|
||||||
|
| 旧 `removeLocalHistoryPath` | 改为调上述 API | 同上 | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 分阶段任务(可独立验收)
|
||||||
|
|
||||||
|
### Phase 0:契约文档 — 已完成
|
||||||
|
|
||||||
|
- [x] 本文档
|
||||||
|
- [x] `API-PAGINATION.md` 链接与 `/file` 推荐说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 1:IndexedDB 模块(1.5h)
|
||||||
|
|
||||||
|
**新建** [`frontend-web/js/common/soon-local-store.js`](frontend-web/js/common/soon-local-store.js)
|
||||||
|
|
||||||
|
| 任务 ID | 任务 | 细节 |
|
||||||
|
|---------|------|------|
|
||||||
|
| P1.1 | `soonLocalOpen` | `indexedDB.open('soondesign_local', 1)`;`onupgradeneeded` 建 `blobs`/`thumbs` |
|
||||||
|
| P1.2 | `soonLocalGet/Put` | `json` 存对象;`bytes` 估算 `JSON.stringify` 长度 |
|
||||||
|
| P1.3 | `soonLocalPutThumb/GetThumb` | thumbs store |
|
||||||
|
| P1.4 | `soonLocalRemove` | 同时删 blobs+thumbs |
|
||||||
|
| P1.5 | `soonLocalRenameKey` | 读旧→写新→删旧(session→cloud) |
|
||||||
|
| P1.6 | `soonLocalEvictLRU` | 按 `savedAt` 升序删至 ≤20 |
|
||||||
|
| P1.7 | `soonExtractThumbFromJson` | §1.5 |
|
||||||
|
| P1.8 | 降级 | `indexedDB` 不可用 → 所有 get 返回 null,put 静默失败 |
|
||||||
|
| P1.9 | HTML 引入 | 三页在 `web.js` 前引入 |
|
||||||
|
|
||||||
|
**验收**
|
||||||
|
|
||||||
|
- [ ] DevTools Application → IDB 可见 stores
|
||||||
|
- [ ] put/get 往返;第 21 条触发 evict
|
||||||
|
- [ ] 隐私模式不抛未捕获异常
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 2:web.js 读写穿透(2h)
|
||||||
|
|
||||||
|
**改** [`frontend-web/js/platform/web.js`](frontend-web/js/platform/web.js)
|
||||||
|
|
||||||
|
| 任务 ID | 任务 | 细节 |
|
||||||
|
|---------|------|------|
|
||||||
|
| P2.1 | `resolveCacheKey(key)` | 模板/云/ session 统一;云用完整 `soondesign_file:id:vN` |
|
||||||
|
| P2.2 | read 模板 stale | §1.4;miss 走 `GET .../templates/{id}/file` + `text()` + `JSON.parse` |
|
||||||
|
| P2.3 | read 云文件 | IDB miss → 现有 `authedFetch files/{id}`;成功后 put |
|
||||||
|
| P2.4 | read session | IDB → sessionStorage 兜底(过渡期) |
|
||||||
|
| P2.5 | read 后写 thumb | `soonExtractThumbFromJson` + `soonLocalPutThumb` |
|
||||||
|
| P2.6 | writeFile 成功 | `soonLocalPut`;云 POST/PUT 成功后若 key 变化 `soonLocalRenameKey` |
|
||||||
|
| P2.7 | write 未登录 | session key + IDB 双写;**停止** `localStorage.setItem(sessionKey, 大JSON)` |
|
||||||
|
|
||||||
|
**验收**
|
||||||
|
|
||||||
|
- [ ] 模板首次:Network 仅 `/templates/{id}/file`;二次 0 请求
|
||||||
|
- [ ] 云文件二次打开无 `GET /files/{id}`
|
||||||
|
- [ ] PUT 后 `openAs.name` 与新 version key 一致且 IDB 可命中
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 3:L1 最近列表 + 保存/打开写 L1(2h)
|
||||||
|
|
||||||
|
**新建** [`frontend-web/js/common/soon-recent.js`](frontend-web/js/common/soon-recent.js)
|
||||||
|
|
||||||
|
| 任务 ID | 任务 | 细节 |
|
||||||
|
|---------|------|------|
|
||||||
|
| P3.1 | `soonRecentUpsert` | 按 `key` 去重;字段 §1.1;`getDate()` 时间格式与现 `saveHistory` 一致 |
|
||||||
|
| P3.2 | `soonRecentList/Remove` | — |
|
||||||
|
| P3.3 | `soonRecentKindFromKey` | `cloud`/`template`/`local` |
|
||||||
|
| P3.4 | `soonRecentMigrateFromHistory` | 读 `soondesign_history`;`path`→`key`;无 thumb;**仅执行一次**(`soondesign_recent_migrated` 标记) |
|
||||||
|
| P3.5 | `onCloudWriteDone` | design1/2 `output.js`:upsert(`res.fileKey`,`res.name`, soonType) |
|
||||||
|
| P3.6 | `saveHistory` | Web:`soonRecentUpsert`;Electron fs 路径保留 |
|
||||||
|
| P3.7 | `cloud-files` | `soonOpenSoonJsonLocally`、import 成功 upsert |
|
||||||
|
| P3.8 | `openFile` 加载成功 | design1 `doOpenWithJson` 末尾;design2 `saveInitialState` 末尾 upsert |
|
||||||
|
| P3.9 | `index` 开模板 | `openTemplateItem` 设置 `_soonTemplateMeta`(§1.4) |
|
||||||
|
|
||||||
|
**验收**
|
||||||
|
|
||||||
|
- [ ] 云端 Ctrl+S 后回首页立即见卡片
|
||||||
|
- [ ] 打开模板/云文件后 L1 有对应项
|
||||||
|
- [ ] 旧 history 用户升级后 L1 有条目(可无 thumb)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 4:首页 loadHistory 改造(1.5h)
|
||||||
|
|
||||||
|
**改** [`frontend-web/js/index.js`](frontend-web/js/index.js)
|
||||||
|
|
||||||
|
| 任务 ID | 任务 | 细节 |
|
||||||
|
|---------|------|------|
|
||||||
|
| P4.1 | 数据源 | `soonRecentList()` 为主;启动时 `soonRecentMigrateFromHistory` |
|
||||||
|
| P4.2 | 删除 merge 云端追加 | 移除 `mergeRecentItems` 中「云端未见过则 push」逻辑;改为 `enrichFromCloud(items, cloudMeta)` 只补 name/version |
|
||||||
|
| P4.3 | 渲染 thumb | `soonLocalGetThumb(item.key)` → fallback `soonAsset(bg_1/2)`;模板项可用 `/thumb` URL(`updated_at` 作 `?v=`) |
|
||||||
|
| P4.4 | 删除 readJsonFile 循环 | 移除 ~381 行;**删除** `fileExists` 灰显逻辑(改为始终可点;打开时 miss 再拉+toast) |
|
||||||
|
| P4.5 | 删除 | `removeLocalHistoryPath` → `soonRecentRemove` + `soonLocalRemove` |
|
||||||
|
| P4.6 | 刷新 | `pageshow`/`visibilitychange` 调 `loadHistory`(60s 节流 `_recentLoadedAt`) |
|
||||||
|
| P4.7 | 分页 | 保留 `fileListState` 对 L1 items 分页 |
|
||||||
|
|
||||||
|
**验收**
|
||||||
|
|
||||||
|
- [ ] 首页 Network:0× 全量 `files/{id}` / `templates/{id}`
|
||||||
|
- [ ] 12 卡仅 thumb 图或 0 额外请求(IDB 命中)
|
||||||
|
- [ ] 从设计页返回可见新保存项
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 5:session 迁移与清理(1h)
|
||||||
|
|
||||||
|
| 任务 ID | 任务 | 细节 |
|
||||||
|
|---------|------|------|
|
||||||
|
| P5.1 | `soonPutSoonSession` | 正文 IDB;sessionStorage 仅存 `{key}` 标记或短指针 |
|
||||||
|
| P5.2 | `readJsonFile` session | 优先 IDB |
|
||||||
|
| P5.3 | `soonTryConsumeCloudImport` | 成功:`soonLocalRenameKey` + `soonRecentUpsert` + 删 session |
|
||||||
|
| P5.4 | 清理 | 移除 `cloud-files`/`index` 对大 JSON 的 localStorage 写入 |
|
||||||
|
| P5.5 | `index` 删卡 | 清 sessionStorage 旧 key(保留现有 769 行逻辑并接 IDB) |
|
||||||
|
|
||||||
|
**验收**
|
||||||
|
|
||||||
|
- [ ] 本地 .soon → 编辑 → 保存 → key 为 `soondesign_file:*`
|
||||||
|
- [ ] sessionStorage 无 >1MB 字符串
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 6:后端轻量增强(可选,1h)
|
||||||
|
|
||||||
|
| 任务 ID | 文件 | 细节 |
|
||||||
|
|---------|------|------|
|
||||||
|
| P6.1 | `TemplateService::outputFile` | `Cache-Control: public, max-age=86400, must-revalidate` |
|
||||||
|
| P6.2 | `FileController` + 路由 | `GET /api/v1/files/{id}/thumb` |
|
||||||
|
| P6.3 | `FileService` | 上传/更新时 `extractThumb` 入 DB(可复用 `TemplateService` 逻辑);`list` 增 `has_thumb` |
|
||||||
|
| P6.4 | `docs/API-PAGINATION.md` | 登记 thumb 端点 |
|
||||||
|
|
||||||
|
**验收**
|
||||||
|
|
||||||
|
- [ ] `curl -I .../templates/1/file` 含 `max-age=86400`
|
||||||
|
- [ ] 云 thumb 返回 `image/jpeg`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 7:全链路验收(0.5h)
|
||||||
|
|
||||||
|
| # | 场景 | Network | UI |
|
||||||
|
|---|------|---------|-----|
|
||||||
|
| 1 | 首次开模板 | 1× `/templates/{id}/file` | 画布 OK |
|
||||||
|
| 2 | 再次开同模板 | 0 全量 | <1s |
|
||||||
|
| 3 | 模板改 updated_at 后再开 | 1× `/file` | 新内容 |
|
||||||
|
| 4 | 保存新文件(登录) | POST | 回首页有卡 |
|
||||||
|
| 5 | 再次开云文件 | 0 全量 | PUT 可保存 |
|
||||||
|
| 6 | 首页 12 最近 | 0 全量 json | thumb OK |
|
||||||
|
| 7 | 未登录保存 | 无 API | L1+IDB |
|
||||||
|
| 8 | 删云文件卡 | DELETE | L1+IDB 清除 |
|
||||||
|
| 9 | IDB 满 20 再开第 21 | 1× 网络 | 最旧 blob 淘汰 |
|
||||||
|
| 10 | 模板列表 | 1× `/templates` 元数据 | 不变 |
|
||||||
|
|
||||||
|
**部署**:前端 P1~P5 同批;强刷 `soon-local-store.js`/`soon-recent.js`/`web.js`/`index.js`;P6 可稍后。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 改动文件清单
|
||||||
|
|
||||||
|
| 阶段 | 文件 |
|
||||||
|
|------|------|
|
||||||
|
| P1 | `soon-local-store.js`;`pages/index|design1|design2.web.html` |
|
||||||
|
| P2 | `platform/web.js` |
|
||||||
|
| P3 | `soon-recent.js`;`design1/2/output.js`;`cloud-files.js`;`index.js`(meta) |
|
||||||
|
| P4 | `index.js` |
|
||||||
|
| P5 | `cloud-files.js`;`web.js` |
|
||||||
|
| P6 | `TemplateService.php`;`FileController.php`;`FileService.php`;`public/index.php`;`API-PAGINATION.md` |
|
||||||
|
|
||||||
|
**不改动**:`.soon` schema;`design*-back.js`;Electron(后续可选对齐)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 依赖关系
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
P0[P0_done]
|
||||||
|
P1[P1_IDB]
|
||||||
|
P2[P2_web_bridge]
|
||||||
|
P3[P3_recent_L1]
|
||||||
|
P4[P4_index]
|
||||||
|
P5[P5_session]
|
||||||
|
P6[P6_backend_opt]
|
||||||
|
P7[P7_QA]
|
||||||
|
|
||||||
|
P0 --> P1 --> P2 --> P3 --> P4 --> P5 --> P7
|
||||||
|
P2 --> P6 --> P7
|
||||||
|
```
|
||||||
|
|
||||||
|
**顺序**:P1 → P2 → P3 → P4 → P5 → P7;P6 与 P3/P4 并行。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 风险与缓解
|
||||||
|
|
||||||
|
| 风险 | 缓解 |
|
||||||
|
|------|------|
|
||||||
|
| IDB 配额 | LRU 20;thumb ≤100KB;evict 失败 toast |
|
||||||
|
| 多设备 | 云 version 为准;409 提示刷新 |
|
||||||
|
| 模板 stale 漏检 | 首页列表带 `updated_at`;§1.4 |
|
||||||
|
| PUT 后双 version 缓存 | `onCloudWriteDone` rename + 删旧 key |
|
||||||
|
| 隐私模式 | 降级 session+网络,与现网一致 |
|
||||||
|
| 旧用户无 recent | `soonRecentMigrateFromHistory` 一次 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 不在 scope
|
||||||
|
|
||||||
|
- CDN;`.soon` 剥离 base64;Electron IDB;CRDT 离线合并。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**确认后**按 P1→P7 实施;每阶段勾选任务 ID 验收后再进入下一阶段。
|
||||||
@@ -424,6 +424,7 @@ body.soon-design-page,
|
|||||||
body.soon-design-page .layui-fluid.main,
|
body.soon-design-page .layui-fluid.main,
|
||||||
body.soon-design-page .layui-row {
|
body.soon-design-page .layui-row {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
html:has(body.soon-design-page) {
|
html:has(body.soon-design-page) {
|
||||||
@@ -431,15 +432,22 @@ html:has(body.soon-design-page) {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.soon-design-page,
|
||||||
|
body.soon-design-page *,
|
||||||
|
body.soon-design-page *::before,
|
||||||
|
body.soon-design-page *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body.soon-design-page {
|
body.soon-design-page {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
|
max-width: 100vw;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #1e1e1e;
|
background-color: #1e1e1e;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
@@ -448,7 +456,21 @@ body.soon-design-page .soon-design-portal-bar,
|
|||||||
body.soon-design-page .soon-portal-topbar {
|
body.soon-design-page .soon-portal-topbar {
|
||||||
position: static !important;
|
position: static !important;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .soon-portal-topbar__brand {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .soon-portal-topbar__actions {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.soon-design-page #portal-topbar {
|
body.soon-design-page #portal-topbar {
|
||||||
@@ -457,6 +479,7 @@ body.soon-design-page #portal-topbar {
|
|||||||
|
|
||||||
body.soon-design-page .layui-fluid.main {
|
body.soon-design-page .layui-fluid.main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -467,6 +490,7 @@ body.soon-design-page .layui-fluid.main {
|
|||||||
|
|
||||||
body.soon-design-page .layui-row {
|
body.soon-design-page .layui-row {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
@@ -477,7 +501,7 @@ body.soon-design-page .layui-row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.soon-design-page .col-left {
|
body.soon-design-page .col-left {
|
||||||
flex: 1;
|
flex: 1 1 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
float: none;
|
float: none;
|
||||||
@@ -509,15 +533,116 @@ body.soon-design-page .tools-bar {
|
|||||||
background-color: #31373d;
|
background-color: #31373d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .tools-bar.left-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .tools-bar.left-bar .mode-left {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .tools-bar.left-bar .mode-warp {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .tools-bar.left-bar .mode-warp::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .tools-bar.left-bar .mode3 {
|
||||||
|
float: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: normal;
|
||||||
|
margin-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
body.soon-design-page #version_change {
|
body.soon-design-page #version_change {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .soon-side-tabs {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: stretch;
|
||||||
|
margin-left: 10px;
|
||||||
|
height: 32px;
|
||||||
|
border: 1px solid #646E76;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #2a2f35;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .soon-side-tabs .ui-button {
|
||||||
|
margin-left: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
min-width: 48px;
|
||||||
|
height: 100%;
|
||||||
|
width: auto;
|
||||||
|
padding: 0 12px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .soon-side-tabs .ui-button + .ui-button {
|
||||||
|
border-left: 1px solid #646E76;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .soon-side-tabs .ui-button.ui-button-active {
|
||||||
|
background-color: #646E76;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .soon-side-tabs .ui-button:not(.ui-button-active):hover {
|
||||||
|
background-color: #4a535c;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .soon-side-tabs .ui-button.ui-button-active:hover {
|
||||||
|
background-color: #74808a;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.soon-design-page .soon-tool-text-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 32px;
|
||||||
|
min-width: 52px;
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 0 12px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body.soon-design-page .img-warp {
|
body.soon-design-page .img-warp {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -567,15 +692,18 @@ body.soon-design-page .container .right {
|
|||||||
|
|
||||||
body.soon-design-page #canvas-div {
|
body.soon-design-page #canvas-div {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.soon-design-page .canvas-wrapper {
|
body.soon-design-page .canvas-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -45,15 +45,23 @@
|
|||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layui-layer.soon-layer.soon-layer--pay,
|
||||||
|
.layui-layer-soon-layer.soon-layer--pay {
|
||||||
|
width: min(480px, calc(100vw - 32px)) !important;
|
||||||
|
max-width: min(480px, calc(100vw - 32px)) !important;
|
||||||
|
max-height: calc(100vh - 32px) !important;
|
||||||
|
border-radius: var(--soon-radius-lg) !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.layui-layer.soon-layer.soon-layer--pay .layui-layer-content,
|
.layui-layer.soon-layer.soon-layer--pay .layui-layer-content,
|
||||||
.layui-layer-soon-layer.soon-layer--pay .layui-layer-content {
|
.layui-layer-soon-layer.soon-layer--pay .layui-layer-content {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
max-height: none !important;
|
height: auto !important;
|
||||||
}
|
max-height: calc(100vh - 32px) !important;
|
||||||
|
padding: 0 !important;
|
||||||
.layui-layer.soon-layer.soon-layer--pay,
|
box-sizing: border-box !important;
|
||||||
.layui-layer-soon-layer.soon-layer--pay {
|
background: transparent !important;
|
||||||
max-height: calc(100vh - 32px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.layui-layer.soon-layer.soon-layer--subscribe,
|
.layui-layer.soon-layer.soon-layer--subscribe,
|
||||||
@@ -77,6 +85,136 @@
|
|||||||
right: 14px;
|
right: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layui-layer.soon-layer.soon-layer--activate,
|
||||||
|
.layui-layer-soon-layer.soon-layer--activate {
|
||||||
|
width: min(480px, calc(100vw - 32px)) !important;
|
||||||
|
max-width: min(480px, calc(100vw - 32px)) !important;
|
||||||
|
max-height: calc(100vh - 32px) !important;
|
||||||
|
border-radius: var(--soon-radius-lg) !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-layer.soon-layer.soon-layer--activate .layui-layer-content,
|
||||||
|
.layui-layer-soon-layer.soon-layer--activate .layui-layer-content {
|
||||||
|
overflow: visible !important;
|
||||||
|
height: auto !important;
|
||||||
|
max-height: calc(100vh - 32px) !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-layer.soon-layer.soon-layer--activate .layui-layer-setwin,
|
||||||
|
.layui-layer-soon-layer.soon-layer--activate .layui-layer-setwin,
|
||||||
|
.layui-layer.soon-layer.soon-layer--pay .layui-layer-setwin,
|
||||||
|
.layui-layer-soon-layer.soon-layer--pay .layui-layer-setwin,
|
||||||
|
.layui-layer.soon-layer.soon-layer--login-gate .layui-layer-setwin,
|
||||||
|
.layui-layer-soon-layer.soon-layer--login-gate .layui-layer-setwin {
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* title:false 时 layui 用 close2,默认 top/right:-28px 会裁到弹窗外;此处统一自定义关闭钮 */
|
||||||
|
.layui-layer.soon-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico,
|
||||||
|
.layui-layer-soon-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico,
|
||||||
|
.layui-layer.soon-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico,
|
||||||
|
.layui-layer-soon-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico,
|
||||||
|
.layui-layer.soon-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico,
|
||||||
|
.layui-layer-soon-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico,
|
||||||
|
body.soon-design-page .layui-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico,
|
||||||
|
body.soon-design-page .layui-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico,
|
||||||
|
body.soon-design-page .layui-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico {
|
||||||
|
position: relative !important;
|
||||||
|
top: auto !important;
|
||||||
|
right: auto !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 30px !important;
|
||||||
|
height: 30px !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.94) !important;
|
||||||
|
background-image: none !important;
|
||||||
|
filter: none !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
font-size: 0 !important;
|
||||||
|
line-height: 1 !important;
|
||||||
|
text-indent: 0 !important;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
|
||||||
|
transition: background 0.15s, box-shadow 0.15s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-layer.soon-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico:hover,
|
||||||
|
.layui-layer-soon-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico:hover,
|
||||||
|
.layui-layer.soon-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico:hover,
|
||||||
|
.layui-layer-soon-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico:hover,
|
||||||
|
.layui-layer.soon-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico:hover,
|
||||||
|
.layui-layer-soon-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico:hover,
|
||||||
|
body.soon-design-page .layui-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico:hover,
|
||||||
|
body.soon-design-page .layui-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico:hover,
|
||||||
|
body.soon-design-page .layui-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico:hover {
|
||||||
|
background: #fff !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-layer.soon-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico::before,
|
||||||
|
.layui-layer-soon-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico::before,
|
||||||
|
.layui-layer.soon-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico::before,
|
||||||
|
.layui-layer-soon-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico::before,
|
||||||
|
.layui-layer.soon-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico::before,
|
||||||
|
.layui-layer-soon-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico::before,
|
||||||
|
body.soon-design-page .layui-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico::before,
|
||||||
|
body.soon-design-page .layui-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico::before,
|
||||||
|
body.soon-design-page .layui-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico::before {
|
||||||
|
content: '×';
|
||||||
|
display: block;
|
||||||
|
position: static;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: none !important;
|
||||||
|
transform: none;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
color: #1a1f24;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-layer.soon-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico::after,
|
||||||
|
.layui-layer-soon-layer.soon-layer--activate .layui-layer-setwin a.layui-layer-ico::after,
|
||||||
|
.layui-layer.soon-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico::after,
|
||||||
|
.layui-layer-soon-layer.soon-layer--pay .layui-layer-setwin a.layui-layer-ico::after,
|
||||||
|
.layui-layer.soon-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico::after,
|
||||||
|
.layui-layer-soon-layer.soon-layer--login-gate .layui-layer-setwin a.layui-layer-ico::after {
|
||||||
|
display: none !important;
|
||||||
|
content: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-layer.soon-layer.soon-layer--login-gate,
|
||||||
|
.layui-layer-soon-layer.soon-layer--login-gate {
|
||||||
|
width: min(400px, calc(100vw - 32px)) !important;
|
||||||
|
max-height: calc(100vh - 32px) !important;
|
||||||
|
border-radius: var(--soon-radius-lg);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: var(--soon-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-layer.soon-layer.soon-layer--login-gate .layui-layer-content,
|
||||||
|
.layui-layer-soon-layer.soon-layer--login-gate .layui-layer-content {
|
||||||
|
overflow: visible !important;
|
||||||
|
height: auto !important;
|
||||||
|
max-height: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
.layui-layer.soon-layer .layui-layer-content.soon-layer-content--scroll,
|
.layui-layer.soon-layer .layui-layer-content.soon-layer-content--scroll,
|
||||||
.layui-layer-soon-layer .layui-layer-content.soon-layer-content--scroll {
|
.layui-layer-soon-layer .layui-layer-content.soon-layer-content--scroll {
|
||||||
overflow-x: hidden !important;
|
overflow-x: hidden !important;
|
||||||
@@ -149,12 +287,12 @@ body.soon-design-page .layui-layer .layui-layer-content {
|
|||||||
color: var(--soon-text) !important;
|
color: var(--soon-text) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.soon-design-page .layui-layer .layui-layer-setwin .layui-layer-ico {
|
body.soon-design-page .layui-layer:not(.soon-layer--activate):not(.soon-layer--pay):not(.soon-layer--login-gate) .layui-layer-setwin .layui-layer-ico {
|
||||||
filter: brightness(0) invert(1);
|
filter: brightness(0) invert(1);
|
||||||
opacity: 0.88;
|
opacity: 0.88;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.soon-design-page .layui-layer .layui-layer-setwin .layui-layer-ico:hover {
|
body.soon-design-page .layui-layer:not(.soon-layer--activate):not(.soon-layer--pay):not(.soon-layer--login-gate) .layui-layer-setwin .layui-layer-ico:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -803,461 +803,7 @@
|
|||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── 支付弹窗 ── */
|
@import url('pay-sheet.css');
|
||||||
.soon-pay-sheet {
|
|
||||||
padding: 0;
|
|
||||||
text-align: left;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet__head {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 20px;
|
|
||||||
padding: 22px 26px 20px;
|
|
||||||
background: linear-gradient(160deg, rgba(0, 150, 136, 0.12) 0%, rgba(30, 36, 42, 0.95) 55%);
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet__eyebrow {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--soon-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--soon-text-strong);
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet__order {
|
|
||||||
margin: 8px 0 0;
|
|
||||||
font-size: 11px;
|
|
||||||
font-family: ui-monospace, Consolas, monospace;
|
|
||||||
color: var(--soon-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet__head-price {
|
|
||||||
flex-shrink: 0;
|
|
||||||
text-align: right;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet__currency {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--soon-text-muted);
|
|
||||||
vertical-align: top;
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet__amount {
|
|
||||||
font-size: 34px;
|
|
||||||
font-weight: 800;
|
|
||||||
color: var(--soon-text-strong);
|
|
||||||
letter-spacing: -0.03em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet__body {
|
|
||||||
padding: 22px 26px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-stepper {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: center;
|
|
||||||
list-style: none;
|
|
||||||
margin: 0 0 24px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-stepper__item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
width: 72px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-stepper__bridge {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 24px;
|
|
||||||
max-width: 56px;
|
|
||||||
height: 2px;
|
|
||||||
margin-top: 15px;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 1px;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-stepper__bridge.is-done {
|
|
||||||
background: linear-gradient(90deg, var(--soon-accent), rgba(77, 182, 172, 0.6));
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-stepper__dot {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--soon-text-muted);
|
|
||||||
background: rgba(0, 0, 0, 0.35);
|
|
||||||
border: 2px solid rgba(255, 255, 255, 0.12);
|
|
||||||
transition: background 0.2s, border-color 0.2s, color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-stepper__label {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--soon-text-muted);
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-stepper__item.is-active .soon-pay-stepper__dot {
|
|
||||||
color: #fff;
|
|
||||||
background: var(--soon-accent);
|
|
||||||
border-color: var(--soon-accent);
|
|
||||||
box-shadow: 0 0 0 4px rgba(0, 150, 136, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-stepper__item.is-active .soon-pay-stepper__label {
|
|
||||||
color: var(--soon-text-strong);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-stepper__item.is-done .soon-pay-stepper__dot {
|
|
||||||
color: #fff;
|
|
||||||
background: rgba(77, 182, 172, 0.35);
|
|
||||||
border-color: var(--soon-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-stepper__item.is-done .soon-pay-stepper__label {
|
|
||||||
color: var(--soon-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-section {
|
|
||||||
margin-bottom: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-section__title {
|
|
||||||
margin: 0 0 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--soon-text-muted);
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channels {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channels--single {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel.is-fixed {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel.is-fixed:hover {
|
|
||||||
border-color: rgba(255, 255, 255, 0.08);
|
|
||||||
background: rgba(0, 0, 0, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel.is-fixed.is-active:hover {
|
|
||||||
border-color: var(--soon-accent);
|
|
||||||
background: rgba(0, 150, 136, 0.07);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel.is-fixed .soon-pay-channel__radio {
|
|
||||||
border-radius: 50%;
|
|
||||||
background: currentColor;
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel.is-fixed.is-active .soon-pay-channel__radio::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 14px;
|
|
||||||
border: 1.5px solid rgba(255, 255, 255, 0.08);
|
|
||||||
border-radius: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: rgba(0, 0, 0, 0.18);
|
|
||||||
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
|
|
||||||
text-align: left;
|
|
||||||
font: inherit;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel:hover {
|
|
||||||
border-color: rgba(255, 255, 255, 0.16);
|
|
||||||
background: rgba(0, 0, 0, 0.24);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel.is-active {
|
|
||||||
border-color: var(--soon-accent);
|
|
||||||
background: rgba(0, 150, 136, 0.07);
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(0, 150, 136, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel[data-ch="wechat"].is-active {
|
|
||||||
border-color: #07c160;
|
|
||||||
background: rgba(7, 193, 96, 0.08);
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(7, 193, 96, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel[data-ch="alipay"].is-active {
|
|
||||||
border-color: #1677ff;
|
|
||||||
background: rgba(22, 119, 255, 0.08);
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(22, 119, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel__radio {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
|
||||||
flex-shrink: 0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel.is-active .soon-pay-channel__radio {
|
|
||||||
border-color: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel.is-active .soon-pay-channel__radio::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 3px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel[data-ch="wechat"].is-active .soon-pay-channel__radio { color: #07c160; }
|
|
||||||
.soon-pay-channel[data-ch="alipay"].is-active .soon-pay-channel__radio { color: #1677ff; }
|
|
||||||
|
|
||||||
.soon-pay-channel__icon {
|
|
||||||
width: 34px;
|
|
||||||
height: 34px;
|
|
||||||
border-radius: 9px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 800;
|
|
||||||
color: #fff;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel__icon--wechat { background: linear-gradient(135deg, #06ae56, #07c160); }
|
|
||||||
.soon-pay-channel__icon--alipay { background: linear-gradient(135deg, #0958d9, #1677ff); }
|
|
||||||
|
|
||||||
.soon-pay-channel__text {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel__name {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--soon-text-strong);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-channel__hint {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--soon-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-qr-panel {
|
|
||||||
min-height: 168px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet--checkout .soon-pay-qr-panel {
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet--checkout .soon-pay-stepper {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-empty {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 28px 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: rgba(0, 0, 0, 0.22);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-empty__icon {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
color: rgba(255, 255, 255, 0.22);
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-empty__icon svg {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-empty__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--soon-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-empty__desc {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--soon-text-muted);
|
|
||||||
line-height: 1.55;
|
|
||||||
max-width: 260px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-loading {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 24px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: rgba(0, 0, 0, 0.22);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-loading p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--soon-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-ready {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 22px 20px 18px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: rgba(0, 0, 0, 0.22);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-ready--alipay {
|
|
||||||
padding: 18px 16px 14px;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-ready__brand {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 10px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 800;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-ready__brand--wechat { background: linear-gradient(135deg, #06ae56, #07c160); }
|
|
||||||
.soon-pay-checkout-ready__brand--alipay { background: linear-gradient(135deg, #0958d9, #1677ff); }
|
|
||||||
|
|
||||||
.soon-pay-checkout-ready__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--soon-text-strong);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-ready__desc {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--soon-text-muted);
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-ready__qr {
|
|
||||||
padding: 10px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-ready__qr canvas,
|
|
||||||
.soon-pay-checkout-ready__qr img {
|
|
||||||
display: block;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-checkout-ready__btn {
|
|
||||||
min-width: 180px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet__foot {
|
|
||||||
padding: 12px 26px 18px;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet--checkout .soon-pay-sheet__foot {
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-status {
|
|
||||||
margin: 0 0 12px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--soon-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-status--ok { color: #4db6ac; }
|
|
||||||
.soon-pay-status--err { color: var(--soon-danger); }
|
|
||||||
|
|
||||||
.soon-pay-sheet__cta {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-pay-sheet__cta--secondary {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-member-page ::-webkit-scrollbar { width: 8px; height: 8px; }
|
.soon-member-page ::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||||
.soon-member-page ::-webkit-scrollbar-thumb {
|
.soon-member-page ::-webkit-scrollbar-thumb {
|
||||||
|
|||||||
@@ -0,0 +1,472 @@
|
|||||||
|
/* 支付确认弹窗(设计页 / 会员页共用) */
|
||||||
|
.soon-pay-sheet {
|
||||||
|
padding: 0;
|
||||||
|
text-align: left;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: var(--soon-bg-panel);
|
||||||
|
color: var(--soon-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__toolbar {
|
||||||
|
padding: 12px 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__back {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 22px 52px 20px 26px;
|
||||||
|
background: linear-gradient(160deg, rgba(0, 150, 136, 0.12) 0%, rgba(30, 36, 42, 0.95) 55%);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__head-main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__eyebrow {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--soon-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--soon-text-strong);
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__order {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
font-size: 11px;
|
||||||
|
font-family: ui-monospace, Consolas, monospace;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__head-price {
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__currency {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
vertical-align: top;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__amount {
|
||||||
|
font-size: 34px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--soon-text-strong);
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__body {
|
||||||
|
padding: 22px 26px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-stepper {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0 0 24px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-stepper__item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 72px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-stepper__bridge {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 24px;
|
||||||
|
max-width: 56px;
|
||||||
|
height: 2px;
|
||||||
|
margin-top: 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 1px;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-stepper__bridge.is-done {
|
||||||
|
background: linear-gradient(90deg, var(--soon-accent), rgba(77, 182, 172, 0.6));
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-stepper__dot {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.12);
|
||||||
|
transition: background 0.2s, border-color 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-stepper__label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-stepper__item.is-active .soon-pay-stepper__dot {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--soon-accent);
|
||||||
|
border-color: var(--soon-accent);
|
||||||
|
box-shadow: 0 0 0 4px rgba(0, 150, 136, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-stepper__item.is-active .soon-pay-stepper__label {
|
||||||
|
color: var(--soon-text-strong);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-stepper__item.is-done .soon-pay-stepper__dot {
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(77, 182, 172, 0.35);
|
||||||
|
border-color: var(--soon-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-stepper__item.is-done .soon-pay-stepper__label {
|
||||||
|
color: var(--soon-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-section {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-section__title {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channels {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channels--single {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel.is-fixed {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel.is-fixed:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.08);
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel.is-fixed.is-active:hover {
|
||||||
|
border-color: var(--soon-accent);
|
||||||
|
background: rgba(0, 150, 136, 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel.is-fixed .soon-pay-channel__radio {
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel.is-fixed.is-active .soon-pay-channel__radio::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1.5px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
|
||||||
|
text-align: left;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.16);
|
||||||
|
background: rgba(0, 0, 0, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel.is-active {
|
||||||
|
border-color: var(--soon-accent);
|
||||||
|
background: rgba(0, 150, 136, 0.07);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(0, 150, 136, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel[data-ch="wechat"].is-active {
|
||||||
|
border-color: #07c160;
|
||||||
|
background: rgba(7, 193, 96, 0.08);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(7, 193, 96, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel[data-ch="alipay"].is-active {
|
||||||
|
border-color: #1677ff;
|
||||||
|
background: rgba(22, 119, 255, 0.08);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(22, 119, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel__radio {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel.is-active .soon-pay-channel__radio {
|
||||||
|
border-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel.is-active .soon-pay-channel__radio::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 3px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel[data-ch="wechat"].is-active .soon-pay-channel__radio { color: #07c160; }
|
||||||
|
.soon-pay-channel[data-ch="alipay"].is-active .soon-pay-channel__radio { color: #1677ff; }
|
||||||
|
|
||||||
|
.soon-pay-channel__icon {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 9px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel__icon--wechat { background: linear-gradient(135deg, #06ae56, #07c160); }
|
||||||
|
.soon-pay-channel__icon--alipay { background: linear-gradient(135deg, #0958d9, #1677ff); }
|
||||||
|
|
||||||
|
.soon-pay-channel__text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel__name {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--soon-text-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-channel__hint {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-qr-panel {
|
||||||
|
min-height: 168px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet--checkout .soon-pay-qr-panel {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet--checkout .soon-pay-stepper {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 28px 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.22);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-empty__icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
color: rgba(255, 255, 255, 0.22);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-empty__icon svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-empty__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--soon-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-empty__desc {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
line-height: 1.55;
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.22);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-loading p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-ready {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 22px 20px 18px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.22);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-ready--alipay {
|
||||||
|
padding: 18px 16px 14px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-ready__brand {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-ready__brand--wechat { background: linear-gradient(135deg, #06ae56, #07c160); }
|
||||||
|
.soon-pay-checkout-ready__brand--alipay { background: linear-gradient(135deg, #0958d9, #1677ff); }
|
||||||
|
|
||||||
|
.soon-pay-checkout-ready__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--soon-text-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-ready__desc {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-ready__qr {
|
||||||
|
padding: 10px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-ready__qr canvas,
|
||||||
|
.soon-pay-checkout-ready__qr img {
|
||||||
|
display: block;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-checkout-ready__btn {
|
||||||
|
min-width: 180px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__foot {
|
||||||
|
padding: 12px 26px 18px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
margin-top: 16px;
|
||||||
|
background: rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet--checkout .soon-pay-sheet__foot {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-status {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-status--ok { color: #4db6ac; }
|
||||||
|
.soon-pay-status--err { color: var(--soon-danger); }
|
||||||
|
|
||||||
|
.soon-pay-sheet__cta {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-pay-sheet__cta--secondary {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@import url('pay-sheet.css');
|
||||||
|
|
||||||
.soon-subscribe-modal {
|
.soon-subscribe-modal {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background: var(--soon-bg-panel);
|
background: var(--soon-bg-panel);
|
||||||
@@ -223,31 +225,149 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.soon-login-gate {
|
||||||
|
text-align: left;
|
||||||
|
background: var(--soon-bg-panel);
|
||||||
|
color: var(--soon-text);
|
||||||
|
border-radius: var(--soon-radius-lg);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__head {
|
||||||
|
padding: 22px 48px 16px 22px;
|
||||||
|
border-bottom: 1px solid var(--soon-border-subtle);
|
||||||
|
background: linear-gradient(160deg, rgba(0, 150, 136, 0.12) 0%, transparent 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__eyebrow {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--soon-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--soon-text-strong);
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__desc {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.55;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.soon-login-gate__form {
|
.soon-login-gate__form {
|
||||||
padding: 0 24px 8px;
|
padding: 18px 22px 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.soon-login-gate__form .soon-input-wrap {
|
.soon-login-gate__field.soon-input-wrap {
|
||||||
width: 100%;
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__input.soon-input {
|
||||||
|
height: 42px;
|
||||||
|
padding: 0 14px 0 40px;
|
||||||
|
background: var(--soon-bg-deep);
|
||||||
|
border: 1px solid var(--soon-border-subtle);
|
||||||
|
border-radius: var(--soon-radius-sm);
|
||||||
|
color: var(--soon-text-strong);
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.15s, box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__input.soon-input::placeholder {
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__input.soon-input:focus {
|
||||||
|
border-color: var(--soon-accent);
|
||||||
|
box-shadow: var(--soon-focus-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__input.soon-input:-webkit-autofill,
|
||||||
|
.soon-login-gate__input.soon-input:-webkit-autofill:hover,
|
||||||
|
.soon-login-gate__input.soon-input:-webkit-autofill:focus {
|
||||||
|
-webkit-text-fill-color: var(--soon-text-strong);
|
||||||
|
caret-color: var(--soon-text-strong);
|
||||||
|
transition: background-color 99999s ease-out 0s;
|
||||||
|
-webkit-box-shadow: 0 0 0 1000px var(--soon-bg-deep) inset;
|
||||||
|
box-shadow: 0 0 0 1000px var(--soon-bg-deep) inset;
|
||||||
|
border: 1px solid var(--soon-border-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.soon-login-gate__error {
|
.soon-login-gate__error {
|
||||||
color: #f87171;
|
|
||||||
font-size: 13px;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: var(--soon-radius-sm);
|
||||||
|
background: rgba(229, 115, 115, 0.12);
|
||||||
|
border: 1px solid rgba(229, 115, 115, 0.28);
|
||||||
|
color: #fca5a5;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__error[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__submit {
|
||||||
|
margin-top: 2px;
|
||||||
|
height: 42px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.soon-login-gate__links {
|
.soon-login-gate__links {
|
||||||
padding: 0 24px 16px;
|
margin: 0;
|
||||||
|
padding: 0 0 4px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: rgba(255, 255, 255, 0.65);
|
text-align: center;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.soon-login-gate__links a {
|
.soon-login-gate__links a {
|
||||||
color: #7dd3fc;
|
color: var(--soon-accent-hover);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__links a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__foot {
|
||||||
|
padding: 12px 22px 18px;
|
||||||
|
border-top: 1px solid var(--soon-border-subtle);
|
||||||
|
background: rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__stay {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 9px 16px;
|
||||||
|
border: 1px solid var(--soon-border-subtle);
|
||||||
|
border-radius: var(--soon-radius-sm);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--soon-text-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-login-gate__stay:hover {
|
||||||
|
color: var(--soon-text);
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.soon-activate-price {
|
.soon-activate-price {
|
||||||
@@ -265,12 +385,6 @@
|
|||||||
color: rgba(255, 255, 255, 0.55);
|
color: rgba(255, 255, 255, 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
.soon-activate-price__value {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-activate-price__hint {
|
.soon-activate-price__hint {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
@@ -283,25 +397,69 @@
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.soon-activate-channel {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 8px 14px;
|
|
||||||
border-radius: 999px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-activate-channel.is-active {
|
|
||||||
border-color: #38bdf8;
|
|
||||||
background: rgba(56, 189, 248, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.soon-activate-load {
|
.soon-activate-load {
|
||||||
margin: 12px 0 0;
|
margin: 12px 0 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: rgba(255, 255, 255, 0.55);
|
color: rgba(255, 255, 255, 0.55);
|
||||||
min-height: 18px;
|
min-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.soon-activate-modal {
|
||||||
|
border-radius: var(--soon-radius-lg);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-activate-modal .soon-subscribe-modal__head {
|
||||||
|
padding: 22px 52px 18px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-activate-modal .soon-subscribe-modal__body {
|
||||||
|
padding: 18px 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-activate-modal .soon-subscribe-modal__foot {
|
||||||
|
padding: 16px 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-activate-price__value {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--soon-text-strong);
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-activate-channel {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1.5px solid rgba(255, 255, 255, 0.1);
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--soon-text);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-activate-channel input[type="radio"] {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-activate-channel.is-active {
|
||||||
|
border-color: #1677ff;
|
||||||
|
background: rgba(22, 119, 255, 0.1);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(22, 119, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.soon-activate-channel.is-active::before {
|
||||||
|
content: '';
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #1677ff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,4 +38,122 @@
|
|||||||
var n = String(rel || '').replace(/^\/+/, '');
|
var n = String(rel || '').replace(/^\/+/, '');
|
||||||
return window.SOON_JS_BASE + n;
|
return window.SOON_JS_BASE + n;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 等 HTMLImage 解码完成后再 toDataURL / setSrc,避免间歇性空白背景 */
|
||||||
|
window.soonFabricImageWhenReady = function (image, cb, skipEmbed) {
|
||||||
|
if (!image) {
|
||||||
|
if (typeof cb === 'function') cb(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function finalize() {
|
||||||
|
if (skipEmbed) {
|
||||||
|
if (typeof cb === 'function') cb(image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var dataUrl = image.toDataURL();
|
||||||
|
if (dataUrl && dataUrl.length > 500) {
|
||||||
|
image.setSrc(dataUrl, function (img) {
|
||||||
|
if (typeof cb === 'function') cb(img || image);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
if (typeof cb === 'function') cb(image);
|
||||||
|
}
|
||||||
|
var el = image.getElement ? image.getElement() : null;
|
||||||
|
if (!el) {
|
||||||
|
finalize();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (el.complete && el.naturalWidth > 0) {
|
||||||
|
finalize();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.onload = function () { finalize(); };
|
||||||
|
el.onerror = function () {
|
||||||
|
if (typeof cb === 'function') cb(null);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
window.soonFabricImageFromAsset = function (assetName, onReady, onFail, retryLeft, skipEmbed) {
|
||||||
|
if (typeof fabric === 'undefined' || !fabric.Image) {
|
||||||
|
if (typeof onFail === 'function') onFail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var retries = retryLeft == null ? 2 : retryLeft;
|
||||||
|
var url = window.soonAsset(assetName);
|
||||||
|
fabric.Image.fromURL(url, function (image) {
|
||||||
|
if (!image || !image.width) {
|
||||||
|
if (retries > 0) {
|
||||||
|
setTimeout(function () {
|
||||||
|
window.soonFabricImageFromAsset(assetName, onReady, onFail, retries - 1, skipEmbed);
|
||||||
|
}, 150);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof onFail === 'function') onFail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function deliver(img) {
|
||||||
|
if (img && typeof onReady === 'function') onReady(img);
|
||||||
|
else if (retries > 0) {
|
||||||
|
setTimeout(function () {
|
||||||
|
window.soonFabricImageFromAsset(assetName, onReady, onFail, retries - 1, skipEmbed);
|
||||||
|
}, 150);
|
||||||
|
} else if (typeof onFail === 'function') onFail();
|
||||||
|
}
|
||||||
|
if (skipEmbed) {
|
||||||
|
window.soonFabricImageWhenReady(image, deliver, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.soonFabricImageWhenReady(image, deliver);
|
||||||
|
}, null, { crossOrigin: 'anonymous' });
|
||||||
|
};
|
||||||
|
|
||||||
|
window.soonRunWhenCanvasReady = function (selector, fn) {
|
||||||
|
function tryRun() {
|
||||||
|
var el = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
||||||
|
var w = el ? (el.clientWidth || el.width || 0) : 0;
|
||||||
|
if ((!w || w < 50) && window.SOON_DEPLOY_CONFIG) {
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
requestAnimationFrame(tryRun);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof fn === 'function') fn(w || 800);
|
||||||
|
}
|
||||||
|
tryRun();
|
||||||
|
};
|
||||||
|
|
||||||
|
var SOON_LOCALE_CODES = { zh: 1, ozh: 1, en: 1 };
|
||||||
|
|
||||||
|
window.soonNormalizeLocaleCode = function (loc) {
|
||||||
|
if (!loc) return 'zh';
|
||||||
|
if (SOON_LOCALE_CODES[loc]) return loc;
|
||||||
|
var s = String(loc);
|
||||||
|
if (s.indexOf('zh') === 0) return s.indexOf('TW') >= 0 ? 'ozh' : 'zh';
|
||||||
|
return 'en';
|
||||||
|
};
|
||||||
|
|
||||||
|
window.soonResolveLocale = function (cb) {
|
||||||
|
if (typeof cb !== 'function') return;
|
||||||
|
var stored;
|
||||||
|
try { stored = localStorage.getItem('lang'); } catch (e) { stored = null; }
|
||||||
|
if (stored && SOON_LOCALE_CODES[stored]) {
|
||||||
|
cb(stored);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var bridge = window.platformBridge;
|
||||||
|
if (!bridge || typeof bridge.getLocale !== 'function') {
|
||||||
|
cb('zh');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var ret;
|
||||||
|
try { ret = bridge.getLocale(); } catch (e) { cb('zh'); return; }
|
||||||
|
if (ret && typeof ret.then === 'function') {
|
||||||
|
ret.then(function (loc) { cb(window.soonNormalizeLocaleCode(loc)); }).catch(function () { cb('zh'); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb(window.soonNormalizeLocaleCode(ret));
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -39,18 +39,243 @@
|
|||||||
return FILE_PREFIX + id + ':v' + version;
|
return FILE_PREFIX + id + ':v' + version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function soonIsTemplateKey(key) {
|
||||||
|
return !!(key && typeof key === 'string' && key.indexOf(TEMPLATE_PREFIX) === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonNeedsSaveDialog(key) {
|
||||||
|
return !key || soonIsTemplateKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonDefaultNewSoonName() {
|
||||||
|
var d = new Date();
|
||||||
|
function pad(n) { return n < 10 ? '0' + n : String(n); }
|
||||||
|
var yy = String(d.getFullYear()).slice(-2);
|
||||||
|
var stamp = yy + pad(d.getMonth() + 1) + pad(d.getDate()) +
|
||||||
|
pad(d.getHours()) + pad(d.getMinutes()) + pad(d.getSeconds());
|
||||||
|
return 'design' + stamp + '.soon';
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonEnsureSoonExt(name) {
|
||||||
|
var n = String(name || 'design.soon').trim();
|
||||||
|
if (!/\.soon$/i.test(n)) n = (n.replace(/\.soon$/i, '') || 'design') + '.soon';
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonSessionSlug(nameOrKey) {
|
||||||
|
var s = String(nameOrKey || 'design').replace(/^soondesign_session:/, '').split(/[/\\]/).pop();
|
||||||
|
s = s.replace(/\.soon$/i, '');
|
||||||
|
s = s.replace(/-\d{10,}$/, '');
|
||||||
|
return s || 'design';
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonMakeSessionKey(nameOrKey) {
|
||||||
|
return 'soondesign_session:' + soonSessionSlug(nameOrKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonSessionDisplayName(keyOrName) {
|
||||||
|
return soonEnsureSoonExt(soonSessionSlug(keyOrName));
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonBuildSavePreviewPic(canvas) {
|
||||||
|
var THUMB_MAX = 102400;
|
||||||
|
if (!canvas || typeof canvas.getObjects !== 'function') return '';
|
||||||
|
var hidden = [];
|
||||||
|
canvas.getObjects().forEach(function (obj) {
|
||||||
|
if (obj.isGuideLine) {
|
||||||
|
hidden.push(obj);
|
||||||
|
obj.visible = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (hidden.length) canvas.renderAll();
|
||||||
|
var mult = 0.18;
|
||||||
|
var quality = 0.72;
|
||||||
|
var url = '';
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
try {
|
||||||
|
var candidate = canvas.toDataURL({ format: 'jpeg', quality: quality, multiplier: mult });
|
||||||
|
if (candidate && candidate.indexOf('data:image/') === 0 && candidate.length <= THUMB_MAX) {
|
||||||
|
url = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
quality -= 0.18;
|
||||||
|
mult -= 0.04;
|
||||||
|
}
|
||||||
|
hidden.forEach(function (obj) { obj.visible = true; });
|
||||||
|
if (hidden.length) canvas.renderAll();
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonApplySavePreview(con_o, canvas) {
|
||||||
|
if (!con_o) return con_o;
|
||||||
|
var pic = soonBuildSavePreviewPic(canvas);
|
||||||
|
if (pic) con_o.frontDisplayPic = pic;
|
||||||
|
return con_o;
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonSaveDialogDefaultPath(openKey) {
|
||||||
|
if (soonNeedsSaveDialog(openKey)) return soonDefaultNewSoonName();
|
||||||
|
return openKey || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonEnsureSaveFileName(fp) {
|
||||||
|
var name = String(fp || 'design.soon');
|
||||||
|
if (typeof window !== 'undefined' && window.platformBridge && window.fs == null) {
|
||||||
|
return soonEnsureSoonExt(name);
|
||||||
|
}
|
||||||
|
if (typeof window !== 'undefined' && window.path && window.path.extname) {
|
||||||
|
if (window.path.extname(name) !== '.soon') {
|
||||||
|
name = (name.replace(/\.soon$/i, '') || 'design') + '.soon';
|
||||||
|
}
|
||||||
|
} else if (!/\.soon$/i.test(name)) {
|
||||||
|
name = (name.replace(/\.soon$/i, '') || 'design') + '.soon';
|
||||||
|
}
|
||||||
|
return soonEnsureSoonExt(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonReportSaveError(err) {
|
||||||
|
if (typeof window.soonShowApiError === 'function') {
|
||||||
|
window.soonShowApiError({
|
||||||
|
status: err && err.status,
|
||||||
|
message: (err && err.message) || '保存失败',
|
||||||
|
code: err && err.code
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
soonToast((err && err.message) || '保存失败', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonWriteSoonContentWeb(fp, content, prevOpenKey, onDone, onError) {
|
||||||
|
if (!window.platformBridge || !window.platformBridge.writeFile) return false;
|
||||||
|
window.platformBridge.writeFile(fp, content).then(function (res) {
|
||||||
|
if (typeof onDone === 'function') onDone(res, prevOpenKey);
|
||||||
|
}).catch(function (err) {
|
||||||
|
if (typeof onError === 'function') onError(err);
|
||||||
|
else soonReportSaveError(err);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonDownloadBlob(filename, content, mime) {
|
||||||
|
var name = soonEnsureSoonExt(filename || 'design.soon');
|
||||||
|
var blob = content instanceof Blob
|
||||||
|
? content
|
||||||
|
: new Blob([content], { type: mime || 'application/json' });
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.download = name;
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonDownloadRecentItem(item) {
|
||||||
|
if (!item) return Promise.resolve();
|
||||||
|
var key = item.filePath || item.key || '';
|
||||||
|
var name = soonEnsureSoonExt(item.name || 'design.soon');
|
||||||
|
var kind = item.kind || (typeof window.soonRecentKindFromKey === 'function'
|
||||||
|
? window.soonRecentKindFromKey(key) : 'local');
|
||||||
|
|
||||||
|
if (kind === 'template') {
|
||||||
|
var tpl = soonParseTemplateKey(key);
|
||||||
|
if (!tpl) {
|
||||||
|
soonToast('无法下载模板', 'error');
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
var base = (window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base) || '';
|
||||||
|
if (!base) {
|
||||||
|
soonToast('下载地址未配置', 'error');
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return fetch(base + '/templates/' + tpl.id + '/file', { headers: { Accept: 'application/json' } })
|
||||||
|
.then(function (r) {
|
||||||
|
if (!r.ok) throw new Error('download_failed');
|
||||||
|
return r.text();
|
||||||
|
})
|
||||||
|
.then(function (text) {
|
||||||
|
soonDownloadBlob(name, text, 'application/json');
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
soonToast('模板下载失败', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === 'cloud') {
|
||||||
|
function tryCloudApiDownload() {
|
||||||
|
if (!item.fileId || !soonGetAccessToken()) return Promise.resolve(false);
|
||||||
|
var bridge = window.platformBridge;
|
||||||
|
if (!bridge || typeof bridge.downloadCloudFile !== 'function') return Promise.resolve(false);
|
||||||
|
return bridge.downloadCloudFile(item.fileId, name).then(function () { return true; }).catch(function () {
|
||||||
|
soonToast('下载失败', 'error');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (key && typeof window.soonLocalGet === 'function') {
|
||||||
|
return window.soonLocalGet(key).then(function (hit) {
|
||||||
|
if (hit && hit.json) {
|
||||||
|
soonDownloadBlob(name, JSON.stringify(hit.json), 'application/json');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return tryCloudApiDownload().then(function (done) {
|
||||||
|
if (!done) soonToast('请先打开或保存该文件', 'warn');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return tryCloudApiDownload().then(function (done) {
|
||||||
|
if (!done) soonToast('请先打开或保存该文件', 'warn');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.indexOf('soondesign_session:') === 0 || kind === 'local') {
|
||||||
|
if (typeof window.soonLocalGet !== 'function') {
|
||||||
|
soonToast('本地缓存不可用', 'error');
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return window.soonLocalGet(key).then(function (hit) {
|
||||||
|
if (!hit || !hit.json) {
|
||||||
|
soonToast('请先打开或保存该文件', 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
soonDownloadBlob(name, JSON.stringify(hit.json), 'application/json');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
soonToast('无法下载该文件', 'warn');
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLookupRecentCloudName(fileId) {
|
||||||
|
if (!fileId || typeof window.soonRecentList !== 'function') return '';
|
||||||
|
var items = window.soonRecentList();
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
if (items[i].fileId === fileId && items[i].name) return items[i].name;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonResolveCloudFileName(fileKey) {
|
||||||
|
if (!fileKey || fileKey.indexOf(FILE_PREFIX) !== 0) return '';
|
||||||
|
var parsed = soonParseFileKey(fileKey);
|
||||||
|
if (!parsed) return '';
|
||||||
|
var meta = window._soonFileMeta;
|
||||||
|
if (meta && meta.id === parsed.id && meta.name) return meta.name;
|
||||||
|
var recentName = soonLookupRecentCloudName(parsed.id);
|
||||||
|
if (recentName) return recentName;
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function soonNormalizeSoonName(pathOrName) {
|
function soonNormalizeSoonName(pathOrName) {
|
||||||
var n = String(pathOrName || 'design.soon');
|
var n = String(pathOrName || 'design.soon');
|
||||||
if (n.indexOf(FILE_PREFIX) === 0) {
|
if (n.indexOf(FILE_PREFIX) === 0) {
|
||||||
var meta = window._soonFileMeta;
|
var resolved = soonResolveCloudFileName(n);
|
||||||
return (meta && meta.name) ? meta.name : 'design.soon';
|
if (resolved) return resolved;
|
||||||
|
return 'design.soon';
|
||||||
}
|
}
|
||||||
if (n.indexOf(TEMPLATE_PREFIX) === 0) {
|
if (n.indexOf(TEMPLATE_PREFIX) === 0) {
|
||||||
var tpl = soonParseTemplateKey(n);
|
return soonDefaultNewSoonName();
|
||||||
return tpl ? ('template-' + tpl.id + '.soon') : 'template.soon';
|
|
||||||
}
|
}
|
||||||
if (n.indexOf('soondesign_session:') === 0) {
|
if (n.indexOf('soondesign_session:') === 0) {
|
||||||
n = n.replace(/^soondesign_session:/, '');
|
return soonSessionDisplayName(n);
|
||||||
}
|
}
|
||||||
n = n.split(/[/\\]/).pop();
|
n = n.split(/[/\\]/).pop();
|
||||||
if (!/\.soon$/i.test(n)) n = (n.replace(/\.soon$/i, '') || 'design') + '.soon';
|
if (!/\.soon$/i.test(n)) n = (n.replace(/\.soon$/i, '') || 'design') + '.soon';
|
||||||
@@ -128,10 +353,12 @@
|
|||||||
var parsed = soonParseFileKey(fileKey);
|
var parsed = soonParseFileKey(fileKey);
|
||||||
if (!parsed) return;
|
if (!parsed) return;
|
||||||
var prev = window._soonFileMeta || {};
|
var prev = window._soonFileMeta || {};
|
||||||
|
var name = (opts && opts.name) || (prev.id === parsed.id ? prev.name : '') || '';
|
||||||
|
if (!name) name = soonLookupRecentCloudName(parsed.id);
|
||||||
window._soonFileMeta = {
|
window._soonFileMeta = {
|
||||||
id: parsed.id,
|
id: parsed.id,
|
||||||
version: parsed.version != null ? parsed.version : prev.version,
|
version: parsed.version != null ? parsed.version : prev.version,
|
||||||
name: (opts && opts.name) || (prev.id === parsed.id ? prev.name : '') || ''
|
name: name
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,11 +528,15 @@
|
|||||||
|
|
||||||
function soonPutSoonSession(j, fileName) {
|
function soonPutSoonSession(j, fileName) {
|
||||||
if (!j) return '';
|
if (!j) return '';
|
||||||
var hint = (fileName || 'design').replace(/\.soon$/i, '');
|
var displayName = soonEnsureSoonExt(fileName || 'design.soon');
|
||||||
var key = 'soondesign_session:' + hint + '-' + Date.now();
|
var key = soonMakeSessionKey(displayName);
|
||||||
|
if (typeof window.soonLocalCacheAndThumb === 'function') {
|
||||||
|
window.soonLocalCacheAndThumb(key, j, { source: 'session', name: displayName });
|
||||||
|
try { sessionStorage.setItem(key, 'idb'); } catch (e) { /* ignore */ }
|
||||||
|
return key;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
sessionStorage.setItem(key, JSON.stringify(j));
|
sessionStorage.setItem(key, JSON.stringify(j));
|
||||||
try { localStorage.setItem(key, JSON.stringify(j)); } catch (e2) { /* ignore quota */ }
|
|
||||||
return key;
|
return key;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return '';
|
return '';
|
||||||
@@ -318,6 +549,9 @@
|
|||||||
soonToast('无法打开文件(存储空间不足)', 'error');
|
soonToast('无法打开文件(存储空间不足)', 'error');
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
if (typeof window.soonRecentUpsertFromOpen === 'function') {
|
||||||
|
window.soonRecentUpsertFromOpen(key, j);
|
||||||
|
}
|
||||||
soonScheduleCloudImport(key, fileName);
|
soonScheduleCloudImport(key, fileName);
|
||||||
var type = soonSoonTypeFromJson(j);
|
var type = soonSoonTypeFromJson(j);
|
||||||
if (window.platformBridge && window.platformBridge.openDesignPage) {
|
if (window.platformBridge && window.platformBridge.openDesignPage) {
|
||||||
@@ -369,25 +603,55 @@
|
|||||||
if (!pending || pending.sessionKey !== currentFileKey) return;
|
if (!pending || pending.sessionKey !== currentFileKey) return;
|
||||||
try { sessionStorage.removeItem(PENDING_IMPORT_KEY); } catch (e) { /* ignore */ }
|
try { sessionStorage.removeItem(PENDING_IMPORT_KEY); } catch (e) { /* ignore */ }
|
||||||
|
|
||||||
var jsonRaw;
|
function loadJsonForImport() {
|
||||||
|
if (typeof window.soonLocalGet === 'function') {
|
||||||
|
return window.soonLocalGet(currentFileKey).then(function (hit) {
|
||||||
|
if (hit && hit.json) return hit.json;
|
||||||
try {
|
try {
|
||||||
jsonRaw = sessionStorage.getItem(currentFileKey);
|
var raw = sessionStorage.getItem(currentFileKey);
|
||||||
if (!jsonRaw && typeof localStorage !== 'undefined') jsonRaw = localStorage.getItem(currentFileKey);
|
if (raw && raw !== 'idb') return JSON.parse(raw);
|
||||||
} catch (e) {
|
} catch (e) { /* ignore */ }
|
||||||
return;
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var raw = sessionStorage.getItem(currentFileKey);
|
||||||
|
if (raw && raw !== 'idb') return JSON.parse(raw);
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
if (!jsonRaw) return;
|
|
||||||
|
|
||||||
var bridge = window.platformBridge;
|
var bridge = window.platformBridge;
|
||||||
if (!bridge || typeof bridge.importSoonFile !== 'function') return;
|
if (!bridge || typeof bridge.importSoonFile !== 'function') return;
|
||||||
|
|
||||||
bridge.importSoonFile(pending.name || 'design.soon', jsonRaw).then(function (res) {
|
loadJsonForImport().then(function (jsonObj) {
|
||||||
|
if (!jsonObj) return;
|
||||||
|
var oldKey = currentFileKey;
|
||||||
|
return bridge.importSoonFile(pending.name || 'design.soon', jsonObj).then(function (res) {
|
||||||
if (res && res.fileKey && typeof window.openAs !== 'undefined' && window.openAs) {
|
if (res && res.fileKey && typeof window.openAs !== 'undefined' && window.openAs) {
|
||||||
window.openAs.name = res.fileKey;
|
window.openAs.name = res.fileKey;
|
||||||
}
|
}
|
||||||
|
if (res && res.fileKey && typeof window.soonLocalRenameKey === 'function') {
|
||||||
|
window.soonLocalRenameKey(oldKey, res.fileKey, { source: 'cloud', name: res.name || pending.name });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
sessionStorage.removeItem(oldKey);
|
||||||
|
localStorage.removeItem(oldKey);
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
if (typeof window.soonRecentOnCloudSave === 'function') {
|
||||||
|
window.soonRecentOnCloudSave(res, oldKey, soonSoonTypeFromJson(jsonObj));
|
||||||
|
}
|
||||||
|
});
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (err && err.status) return;
|
if (typeof window.soonShowApiError === 'function') {
|
||||||
soonToast((err && err.message) ? err.message : '云端登记失败,可稍后保存重试', 'warn');
|
window.soonShowApiError({
|
||||||
|
status: err && err.status,
|
||||||
|
message: (err && err.message) || '云端登记失败,可稍后保存重试',
|
||||||
|
code: err && err.code
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
soonToast((err && err.message) || '云端登记失败,可稍后保存重试', 'warn');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,13 +726,13 @@
|
|||||||
return tplParsed ? ('模板 #' + tplParsed.id) : pathOrKey;
|
return tplParsed ? ('模板 #' + tplParsed.id) : pathOrKey;
|
||||||
}
|
}
|
||||||
if (pathOrKey.indexOf(FILE_PREFIX) === 0) {
|
if (pathOrKey.indexOf(FILE_PREFIX) === 0) {
|
||||||
var meta = window._soonFileMeta;
|
var resolved = soonResolveCloudFileName(pathOrKey);
|
||||||
if (meta && meta.name) return meta.name;
|
if (resolved) return resolved;
|
||||||
var parsed = soonParseFileKey(pathOrKey);
|
var parsed = soonParseFileKey(pathOrKey);
|
||||||
return parsed ? ('文件 #' + parsed.id) : pathOrKey;
|
return parsed ? ('文件 #' + parsed.id) : pathOrKey;
|
||||||
}
|
}
|
||||||
if (pathOrKey.indexOf('soondesign_session:') === 0) {
|
if (pathOrKey.indexOf('soondesign_session:') === 0) {
|
||||||
return pathOrKey.replace(/^soondesign_session:/, '');
|
return soonSessionDisplayName(pathOrKey);
|
||||||
}
|
}
|
||||||
return String(pathOrKey).split(/[/\\]/).pop();
|
return String(pathOrKey).split(/[/\\]/).pop();
|
||||||
}
|
}
|
||||||
@@ -503,6 +767,7 @@
|
|||||||
window.soonParseFileKey = soonParseFileKey;
|
window.soonParseFileKey = soonParseFileKey;
|
||||||
window.soonMakeFileKey = soonMakeFileKey;
|
window.soonMakeFileKey = soonMakeFileKey;
|
||||||
window.soonNormalizeSoonName = soonNormalizeSoonName;
|
window.soonNormalizeSoonName = soonNormalizeSoonName;
|
||||||
|
window.soonResolveCloudFileName = soonResolveCloudFileName;
|
||||||
window.soonApplyCloudMeta = soonApplyCloudMeta;
|
window.soonApplyCloudMeta = soonApplyCloudMeta;
|
||||||
window.soonBindFileMeta = soonBindFileMeta;
|
window.soonBindFileMeta = soonBindFileMeta;
|
||||||
window.soonSyncOpenNavigation = soonSyncOpenNavigation;
|
window.soonSyncOpenNavigation = soonSyncOpenNavigation;
|
||||||
@@ -529,6 +794,21 @@
|
|||||||
window.soonParseApiError = soonParseApiError;
|
window.soonParseApiError = soonParseApiError;
|
||||||
window.soonShowApiError = soonShowApiError;
|
window.soonShowApiError = soonShowApiError;
|
||||||
window.soonDisplayFileName = soonDisplayFileName;
|
window.soonDisplayFileName = soonDisplayFileName;
|
||||||
|
window.soonIsTemplateKey = soonIsTemplateKey;
|
||||||
|
window.soonNeedsSaveDialog = soonNeedsSaveDialog;
|
||||||
|
window.soonDefaultNewSoonName = soonDefaultNewSoonName;
|
||||||
|
window.soonEnsureSoonExt = soonEnsureSoonExt;
|
||||||
|
window.soonSessionSlug = soonSessionSlug;
|
||||||
|
window.soonMakeSessionKey = soonMakeSessionKey;
|
||||||
|
window.soonSessionDisplayName = soonSessionDisplayName;
|
||||||
|
window.soonBuildSavePreviewPic = soonBuildSavePreviewPic;
|
||||||
|
window.soonApplySavePreview = soonApplySavePreview;
|
||||||
|
window.soonSaveDialogDefaultPath = soonSaveDialogDefaultPath;
|
||||||
|
window.soonEnsureSaveFileName = soonEnsureSaveFileName;
|
||||||
|
window.soonReportSaveError = soonReportSaveError;
|
||||||
|
window.soonWriteSoonContentWeb = soonWriteSoonContentWeb;
|
||||||
|
window.soonDownloadBlob = soonDownloadBlob;
|
||||||
|
window.soonDownloadRecentItem = soonDownloadRecentItem;
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', soonHandlePayReturn);
|
document.addEventListener('DOMContentLoaded', soonHandlePayReturn);
|
||||||
|
|||||||
@@ -141,6 +141,8 @@
|
|||||||
}
|
}
|
||||||
_gate.plan = plan;
|
_gate.plan = plan;
|
||||||
if (statusEl) statusEl.textContent = '';
|
if (statusEl) statusEl.textContent = '';
|
||||||
|
var priceEl = modal.querySelector('.soon-activate-price__value');
|
||||||
|
if (priceEl) priceEl.textContent = '¥' + planPriceDisplay(plan);
|
||||||
updatePayButton(modal);
|
updatePayButton(modal);
|
||||||
}).catch(function () {
|
}).catch(function () {
|
||||||
if (statusEl) statusEl.textContent = '网络错误,请稍后重试';
|
if (statusEl) statusEl.textContent = '网络错误,请稍后重试';
|
||||||
@@ -205,24 +207,36 @@
|
|||||||
try { layer.close(_gate.payIndex); } catch (e) { /* ignore */ }
|
try { layer.close(_gate.payIndex); } catch (e) { /* ignore */ }
|
||||||
}
|
}
|
||||||
_gate = { index: null, payIndex: null, plan: null, gateOpts: opts };
|
_gate = { index: null, payIndex: null, plan: null, gateOpts: opts };
|
||||||
var width = Math.min(520, window.innerWidth - 24);
|
var width = Math.min(480, window.innerWidth - 32);
|
||||||
layer.open({
|
layer.open({
|
||||||
type: 1,
|
type: 1,
|
||||||
skin: 'soon-layer',
|
skin: 'soon-layer',
|
||||||
title: false,
|
title: false,
|
||||||
closeBtn: 1,
|
closeBtn: 1,
|
||||||
|
fixed: true,
|
||||||
|
offset: 'auto',
|
||||||
|
shade: [0.68, '#000'],
|
||||||
shadeClose: true,
|
shadeClose: true,
|
||||||
|
maxWidth: width,
|
||||||
area: [width + 'px', 'auto'],
|
area: [width + 'px', 'auto'],
|
||||||
content: shellHtml(opts.reason, null),
|
content: shellHtml(opts.reason, null),
|
||||||
success: function (layero, index) {
|
success: function (layero, index) {
|
||||||
var layerEl = layero && layero[0] ? layero[0] : layero;
|
var layerEl = layero && layero[0] ? layero[0] : layero;
|
||||||
if (layerEl && layerEl.classList) layerEl.classList.add('soon-layer--subscribe');
|
if (layerEl && layerEl.classList) {
|
||||||
|
layerEl.classList.add('soon-layer--activate');
|
||||||
|
}
|
||||||
var content = layerEl && layerEl.querySelector ? layerEl.querySelector('.layui-layer-content') : null;
|
var content = layerEl && layerEl.querySelector ? layerEl.querySelector('.layui-layer-content') : null;
|
||||||
if (content) content.style.padding = '0';
|
if (content) {
|
||||||
|
content.style.padding = '0';
|
||||||
|
content.style.overflow = 'visible';
|
||||||
|
}
|
||||||
var modal = layerEl.querySelector('.soon-activate-modal');
|
var modal = layerEl.querySelector('.soon-activate-modal');
|
||||||
_gate.index = index;
|
_gate.index = index;
|
||||||
_gate.gateOpts = opts;
|
_gate.gateOpts = opts;
|
||||||
if (modal) bindModal(modal);
|
if (modal) bindModal(modal);
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
if (typeof layer.style === 'function') layer.style(index);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
end: function () {
|
end: function () {
|
||||||
var payIdx = _gate.payIndex;
|
var payIdx = _gate.payIndex;
|
||||||
|
|||||||
@@ -37,21 +37,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function shellHtml(reason) {
|
function shellHtml(reason) {
|
||||||
|
var mailIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16v12H4z"/><path d="M4 8l8 5 8-5"/></svg>';
|
||||||
|
var lockIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="10" rx="1"/><path d="M8 11V8a4 4 0 118 0v3"/></svg>';
|
||||||
return '<div class="soon-login-gate">' +
|
return '<div class="soon-login-gate">' +
|
||||||
'<header class="soon-subscribe-modal__head">' +
|
'<header class="soon-login-gate__head">' +
|
||||||
'<span class="soon-subscribe-modal__eyebrow">登录</span>' +
|
'<span class="soon-login-gate__eyebrow">登录</span>' +
|
||||||
'<h3 class="soon-subscribe-modal__title">登录后继续</h3>' +
|
'<h3 class="soon-login-gate__title">登录后继续</h3>' +
|
||||||
'<p class="soon-subscribe-modal__desc">预览内' + esc(reason || '导出或打印') + '需先登录账号。</p>' +
|
'<p class="soon-login-gate__desc">预览内' + esc(reason || '导出或打印') + '成品需先登录账号。</p>' +
|
||||||
'</header>' +
|
'</header>' +
|
||||||
'<form class="soon-login-gate__form" data-role="login-form">' +
|
'<form class="soon-login-gate__form" data-role="login-form">' +
|
||||||
'<div class="soon-input-wrap"><input type="email" name="email" required class="soon-input" placeholder="邮箱" autocomplete="email"></div>' +
|
'<div class="soon-input-wrap soon-login-gate__field">' +
|
||||||
'<div class="soon-input-wrap"><input type="password" name="password" required class="soon-input" placeholder="密码" autocomplete="current-password"></div>' +
|
'<span class="soon-input-icon" aria-hidden="true">' + mailIcon + '</span>' +
|
||||||
'<p class="soon-login-gate__error" data-role="login-error" style="display:none"></p>' +
|
'<input type="email" name="email" required class="soon-input soon-login-gate__input" placeholder="邮箱" autocomplete="email">' +
|
||||||
'<button type="submit" class="soon-btn soon-btn--block">登录</button>' +
|
'</div>' +
|
||||||
'</form>' +
|
'<div class="soon-input-wrap soon-login-gate__field">' +
|
||||||
|
'<span class="soon-input-icon" aria-hidden="true">' + lockIcon + '</span>' +
|
||||||
|
'<input type="password" name="password" required class="soon-input soon-login-gate__input" placeholder="密码" autocomplete="current-password">' +
|
||||||
|
'</div>' +
|
||||||
|
'<p class="soon-login-gate__error" data-role="login-error" hidden></p>' +
|
||||||
|
'<button type="submit" class="soon-btn soon-btn--block soon-login-gate__submit">登录</button>' +
|
||||||
'<p class="soon-login-gate__links">还没有账号?<a href="register.web.html" target="_blank" rel="noopener">立即注册</a></p>' +
|
'<p class="soon-login-gate__links">还没有账号?<a href="register.web.html" target="_blank" rel="noopener">立即注册</a></p>' +
|
||||||
'<footer class="soon-subscribe-modal__foot">' +
|
'</form>' +
|
||||||
'<button type="button" class="soon-subscribe-modal__stay" data-action="stay">继续设计</button>' +
|
'<footer class="soon-login-gate__foot">' +
|
||||||
|
'<button type="button" class="soon-login-gate__stay" data-action="stay">继续设计</button>' +
|
||||||
'</footer></div>';
|
'</footer></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +85,7 @@
|
|||||||
form.addEventListener('submit', function (e) {
|
form.addEventListener('submit', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (errEl) {
|
if (errEl) {
|
||||||
errEl.style.display = 'none';
|
errEl.hidden = true;
|
||||||
errEl.textContent = '';
|
errEl.textContent = '';
|
||||||
}
|
}
|
||||||
var email = (form.email && form.email.value || '').trim();
|
var email = (form.email && form.email.value || '').trim();
|
||||||
@@ -90,7 +98,7 @@
|
|||||||
if (!j.ok || !j.data || !j.data.access_token) {
|
if (!j.ok || !j.data || !j.data.access_token) {
|
||||||
if (errEl) {
|
if (errEl) {
|
||||||
errEl.textContent = (j && j.message) || '登录失败';
|
errEl.textContent = (j && j.message) || '登录失败';
|
||||||
errEl.style.display = 'block';
|
errEl.hidden = false;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -109,7 +117,7 @@
|
|||||||
}).catch(function () {
|
}).catch(function () {
|
||||||
if (errEl) {
|
if (errEl) {
|
||||||
errEl.textContent = '网络错误,请稍后重试';
|
errEl.textContent = '网络错误,请稍后重试';
|
||||||
errEl.style.display = 'block';
|
errEl.hidden = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -122,20 +130,26 @@
|
|||||||
try { layer.close(_gate.index); } catch (e) { /* ignore */ }
|
try { layer.close(_gate.index); } catch (e) { /* ignore */ }
|
||||||
}
|
}
|
||||||
_gate.opts = opts;
|
_gate.opts = opts;
|
||||||
var width = Math.min(420, window.innerWidth - 24);
|
var width = Math.min(400, window.innerWidth - 32);
|
||||||
layer.open({
|
layer.open({
|
||||||
type: 1,
|
type: 1,
|
||||||
skin: 'soon-layer',
|
skin: 'soon-layer',
|
||||||
title: false,
|
title: false,
|
||||||
closeBtn: 1,
|
closeBtn: 1,
|
||||||
|
shade: [0.62, '#000'],
|
||||||
shadeClose: true,
|
shadeClose: true,
|
||||||
area: [width + 'px', 'auto'],
|
area: [width + 'px', 'auto'],
|
||||||
|
offset: 'auto',
|
||||||
content: shellHtml(opts.reason),
|
content: shellHtml(opts.reason),
|
||||||
success: function (layero, index) {
|
success: function (layero, index) {
|
||||||
var layerEl = layero && layero[0] ? layero[0] : layero;
|
var layerEl = layero && layero[0] ? layero[0] : layero;
|
||||||
if (layerEl && layerEl.classList) layerEl.classList.add('soon-layer--subscribe');
|
if (layerEl && layerEl.classList) layerEl.classList.add('soon-layer--login-gate');
|
||||||
var content = layerEl && layerEl.querySelector ? layerEl.querySelector('.layui-layer-content') : null;
|
var content = layerEl && layerEl.querySelector ? layerEl.querySelector('.layui-layer-content') : null;
|
||||||
if (content) content.style.padding = '0';
|
if (content) {
|
||||||
|
content.style.padding = '0';
|
||||||
|
content.style.overflow = 'visible';
|
||||||
|
content.style.background = 'transparent';
|
||||||
|
}
|
||||||
var modal = layerEl.querySelector('.soon-login-gate');
|
var modal = layerEl.querySelector('.soon-login-gate');
|
||||||
_gate.index = index;
|
_gate.index = index;
|
||||||
_gate.opts = opts;
|
_gate.opts = opts;
|
||||||
|
|||||||
@@ -473,22 +473,29 @@
|
|||||||
displayChannels: channels,
|
displayChannels: channels,
|
||||||
});
|
});
|
||||||
var payCtrl = null;
|
var payCtrl = null;
|
||||||
|
var width = Math.min(480, window.innerWidth - 32);
|
||||||
var idx = layer.open({
|
var idx = layer.open({
|
||||||
type: 1,
|
type: 1,
|
||||||
skin: 'soon-layer',
|
skin: 'soon-layer',
|
||||||
title: false,
|
title: false,
|
||||||
closeBtn: 1,
|
closeBtn: 1,
|
||||||
|
fixed: true,
|
||||||
|
offset: 'auto',
|
||||||
|
shade: [0.72, '#000'],
|
||||||
shadeClose: !!opts.shadeClose,
|
shadeClose: !!opts.shadeClose,
|
||||||
area: ['480px'],
|
maxWidth: width,
|
||||||
|
area: [width + 'px', 'auto'],
|
||||||
content: html,
|
content: html,
|
||||||
success: function (layero) {
|
success: function (layero, index) {
|
||||||
var layerEl = layero && layero[0] ? layero[0] : layero;
|
var layerEl = layero && layero[0] ? layero[0] : layero;
|
||||||
if (layerEl && layerEl.classList) {
|
if (layerEl && layerEl.classList) {
|
||||||
layerEl.classList.add('soon-layer--pay');
|
layerEl.classList.add('soon-layer--pay');
|
||||||
var layerContent = layerEl.querySelector('.layui-layer-content');
|
var layerContent = layerEl.querySelector('.layui-layer-content');
|
||||||
if (layerContent) {
|
if (layerContent) {
|
||||||
|
layerContent.style.padding = '0';
|
||||||
layerContent.style.overflow = 'visible';
|
layerContent.style.overflow = 'visible';
|
||||||
layerContent.style.maxHeight = 'none';
|
layerContent.style.maxHeight = 'none';
|
||||||
|
layerContent.style.background = 'transparent';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var sheet = layerEl.querySelector('.soon-pay-sheet');
|
var sheet = layerEl.querySelector('.soon-pay-sheet');
|
||||||
@@ -505,6 +512,9 @@
|
|||||||
},
|
},
|
||||||
onBack: opts.onBack,
|
onBack: opts.onBack,
|
||||||
});
|
});
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
if (typeof layer.style === 'function') layer.style(index);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
end: function () {
|
end: function () {
|
||||||
if (payCtrl) payCtrl.destroy();
|
if (payCtrl) payCtrl.destroy();
|
||||||
|
|||||||
@@ -19,8 +19,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initClearCache() {
|
||||||
|
var btn = document.getElementById('auth_clear_cache');
|
||||||
|
if (!btn) return;
|
||||||
|
btn.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (typeof layui === 'undefined' || typeof window.soonClearLocalDesignCache !== 'function') return;
|
||||||
|
layui.use('layer', function () {
|
||||||
|
var layer = layui.layer;
|
||||||
|
layer.open({
|
||||||
|
type: 1,
|
||||||
|
skin: 'soon-layer',
|
||||||
|
title: '清除本地缓存',
|
||||||
|
content: '<div class="soon-dialog-body">将清除最近文件与本地缓存,不会退出登录。未上传的本地编辑将丢失。</div>',
|
||||||
|
btn: ['确定清除', '取消'],
|
||||||
|
btnAlign: 'r',
|
||||||
|
area: ['320px', 'auto'],
|
||||||
|
shadeClose: true,
|
||||||
|
yes: function (index) {
|
||||||
|
layer.close(index);
|
||||||
|
window.soonClearLocalDesignCache().then(function () {
|
||||||
|
if (typeof window.soonToast === 'function') window.soonToast('已清除本地缓存', 'success');
|
||||||
|
else layer.msg('已清除本地缓存', { icon: 1, time: 1200 });
|
||||||
|
setTimeout(function () {
|
||||||
|
if (/design[12]\.web\.html/i.test(location.pathname || '')) location.href = 'index.web.html';
|
||||||
|
else location.reload();
|
||||||
|
}, 400);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function initTopbar(options) {
|
function initTopbar(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
initClearCache();
|
||||||
|
|
||||||
var tok = localStorage.getItem('soon_access') || '';
|
var tok = localStorage.getItem('soon_access') || '';
|
||||||
var login = document.getElementById('auth_login');
|
var login = document.getElementById('auth_login');
|
||||||
var reg = document.getElementById('auth_register');
|
var reg = document.getElementById('auth_register');
|
||||||
|
|||||||
@@ -37,7 +37,8 @@
|
|||||||
function toolsBlock(lang, linksHtml) {
|
function toolsBlock(lang, linksHtml) {
|
||||||
return '<div class="soon-portal-topbar__tools">' +
|
return '<div class="soon-portal-topbar__tools">' +
|
||||||
lang +
|
lang +
|
||||||
(linksHtml ? '<nav class="soon-portal-topbar__links" aria-label="导航">' + linksHtml + '</nav>' : '') +
|
(linksHtml ? '<nav class="soon-portal-topbar__links" aria-label="导航">' + linksHtml +
|
||||||
|
'<a href="#" id="auth_clear_cache" class="soon-portal-topbar__link">清除缓存</a></nav>' : '') +
|
||||||
'</div>';
|
'</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var DB_NAME = 'soondesign_local';
|
||||||
|
var DB_VERSION = 1;
|
||||||
|
var MAX_BLOBS = 20;
|
||||||
|
var THUMB_MAX_BYTES = 102400;
|
||||||
|
var dbPromise = null;
|
||||||
|
var idbAvailable = typeof indexedDB !== 'undefined';
|
||||||
|
|
||||||
|
function soonExtractThumbFromJson(json) {
|
||||||
|
if (!json || !json.frontDisplayPic || typeof json.frontDisplayPic !== 'string') return '';
|
||||||
|
var candidate = json.frontDisplayPic.trim();
|
||||||
|
if (candidate.indexOf('data:image/') !== 0) return '';
|
||||||
|
if (candidate.length > THUMB_MAX_BYTES) return '';
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function txDone(tx) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
tx.oncomplete = function () { resolve(); };
|
||||||
|
tx.onerror = function () { reject(tx.error || new Error('idb_tx_error')); };
|
||||||
|
tx.onabort = function () { reject(tx.error || new Error('idb_tx_abort')); };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reqPromise(req) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
req.onsuccess = function () { resolve(req.result); };
|
||||||
|
req.onerror = function () { reject(req.error || new Error('idb_req_error')); };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLocalOpen() {
|
||||||
|
if (!idbAvailable) return Promise.resolve(null);
|
||||||
|
if (dbPromise) return dbPromise;
|
||||||
|
dbPromise = new Promise(function (resolve, reject) {
|
||||||
|
var req = indexedDB.open(DB_NAME, DB_VERSION);
|
||||||
|
req.onupgradeneeded = function (e) {
|
||||||
|
var db = e.target.result;
|
||||||
|
if (!db.objectStoreNames.contains('blobs')) {
|
||||||
|
db.createObjectStore('blobs', { keyPath: 'cacheKey' });
|
||||||
|
}
|
||||||
|
if (!db.objectStoreNames.contains('thumbs')) {
|
||||||
|
db.createObjectStore('thumbs', { keyPath: 'cacheKey' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
req.onsuccess = function () { resolve(req.result); };
|
||||||
|
req.onerror = function () {
|
||||||
|
idbAvailable = false;
|
||||||
|
dbPromise = null;
|
||||||
|
reject(req.error || new Error('idb_open_failed'));
|
||||||
|
};
|
||||||
|
}).catch(function () {
|
||||||
|
idbAvailable = false;
|
||||||
|
dbPromise = null;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return dbPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLocalGet(cacheKey) {
|
||||||
|
if (!cacheKey || !idbAvailable) return Promise.resolve(null);
|
||||||
|
return soonLocalOpen().then(function (db) {
|
||||||
|
if (!db) return null;
|
||||||
|
var tx = db.transaction('blobs', 'readonly');
|
||||||
|
return reqPromise(tx.objectStore('blobs').get(cacheKey)).then(function (row) {
|
||||||
|
if (!row || row.json == null) return null;
|
||||||
|
return {
|
||||||
|
json: row.json,
|
||||||
|
meta: {
|
||||||
|
name: row.name,
|
||||||
|
type: row.type,
|
||||||
|
source: row.source,
|
||||||
|
updatedAt: row.updatedAt,
|
||||||
|
savedAt: row.savedAt,
|
||||||
|
bytes: row.bytes
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}).catch(function () { return null; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLocalEvictLRU(max) {
|
||||||
|
max = max || MAX_BLOBS;
|
||||||
|
return soonLocalOpen().then(function (db) {
|
||||||
|
if (!db) return;
|
||||||
|
var tx = db.transaction(['blobs', 'thumbs'], 'readwrite');
|
||||||
|
var blobStore = tx.objectStore('blobs');
|
||||||
|
return reqPromise(blobStore.getAll()).then(function (rows) {
|
||||||
|
if (!rows || rows.length <= max) return txDone(tx);
|
||||||
|
rows.sort(function (a, b) { return (a.savedAt || 0) - (b.savedAt || 0); });
|
||||||
|
var toRemove = rows.length - max;
|
||||||
|
for (var i = 0; i < toRemove; i++) {
|
||||||
|
var k = rows[i].cacheKey;
|
||||||
|
blobStore.delete(k);
|
||||||
|
tx.objectStore('thumbs').delete(k);
|
||||||
|
}
|
||||||
|
return txDone(tx);
|
||||||
|
});
|
||||||
|
}).catch(function () { /* ignore */ });
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLocalPut(cacheKey, json, meta) {
|
||||||
|
if (!cacheKey || json == null || !idbAvailable) return Promise.resolve(false);
|
||||||
|
meta = meta || {};
|
||||||
|
var jsonObj = typeof json === 'string' ? (function () {
|
||||||
|
try { return JSON.parse(json); } catch (e) { return null; }
|
||||||
|
})() : json;
|
||||||
|
if (!jsonObj) return Promise.resolve(false);
|
||||||
|
var bytes = 0;
|
||||||
|
try { bytes = JSON.stringify(jsonObj).length; } catch (e) { bytes = 0; }
|
||||||
|
var row = {
|
||||||
|
cacheKey: cacheKey,
|
||||||
|
json: jsonObj,
|
||||||
|
name: meta.name || '',
|
||||||
|
type: meta.type != null ? meta.type : (jsonObj.soonType || 1),
|
||||||
|
source: meta.source || '',
|
||||||
|
updatedAt: meta.updatedAt || '',
|
||||||
|
savedAt: Date.now(),
|
||||||
|
bytes: bytes
|
||||||
|
};
|
||||||
|
return soonLocalOpen().then(function (db) {
|
||||||
|
if (!db) return false;
|
||||||
|
var tx = db.transaction('blobs', 'readwrite');
|
||||||
|
tx.objectStore('blobs').put(row);
|
||||||
|
return txDone(tx).then(function () {
|
||||||
|
return soonLocalEvictLRU(MAX_BLOBS).then(function () { return true; });
|
||||||
|
});
|
||||||
|
}).catch(function () { return false; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLocalPutThumb(cacheKey, dataUrl) {
|
||||||
|
if (!cacheKey || !dataUrl || !idbAvailable) return Promise.resolve(false);
|
||||||
|
if (String(dataUrl).length > THUMB_MAX_BYTES) return Promise.resolve(false);
|
||||||
|
return soonLocalOpen().then(function (db) {
|
||||||
|
if (!db) return false;
|
||||||
|
var tx = db.transaction('thumbs', 'readwrite');
|
||||||
|
tx.objectStore('thumbs').put({ cacheKey: cacheKey, dataUrl: dataUrl, savedAt: Date.now() });
|
||||||
|
return txDone(tx).then(function () { return true; });
|
||||||
|
}).catch(function () { return false; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLocalGetThumb(cacheKey) {
|
||||||
|
if (!cacheKey || !idbAvailable) return Promise.resolve('');
|
||||||
|
return soonLocalOpen().then(function (db) {
|
||||||
|
if (!db) return '';
|
||||||
|
var tx = db.transaction('thumbs', 'readonly');
|
||||||
|
return reqPromise(tx.objectStore('thumbs').get(cacheKey)).then(function (row) {
|
||||||
|
return (row && row.dataUrl) ? row.dataUrl : '';
|
||||||
|
});
|
||||||
|
}).catch(function () { return ''; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLocalRemove(cacheKey) {
|
||||||
|
if (!cacheKey || !idbAvailable) return Promise.resolve();
|
||||||
|
return soonLocalOpen().then(function (db) {
|
||||||
|
if (!db) return;
|
||||||
|
var tx = db.transaction(['blobs', 'thumbs'], 'readwrite');
|
||||||
|
tx.objectStore('blobs').delete(cacheKey);
|
||||||
|
tx.objectStore('thumbs').delete(cacheKey);
|
||||||
|
return txDone(tx);
|
||||||
|
}).catch(function () { /* ignore */ });
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLocalRenameKey(oldKey, newKey, meta) {
|
||||||
|
if (!oldKey || !newKey || oldKey === newKey) return Promise.resolve(false);
|
||||||
|
return soonLocalGet(oldKey).then(function (hit) {
|
||||||
|
if (!hit) return false;
|
||||||
|
return soonLocalPut(newKey, hit.json, Object.assign({}, hit.meta, meta || {})).then(function (ok) {
|
||||||
|
if (!ok) return false;
|
||||||
|
return soonLocalGetThumb(oldKey).then(function (thumb) {
|
||||||
|
var chain = Promise.resolve();
|
||||||
|
if (thumb) chain = soonLocalPutThumb(newKey, thumb);
|
||||||
|
return chain.then(function () { return soonLocalRemove(oldKey); }).then(function () { return true; });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).catch(function () { return false; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLocalCacheAndThumb(cacheKey, json, meta) {
|
||||||
|
var thumb = soonExtractThumbFromJson(json);
|
||||||
|
return soonLocalPut(cacheKey, json, meta).then(function (ok) {
|
||||||
|
if (!ok) return false;
|
||||||
|
if (thumb) return soonLocalPutThumb(cacheKey, thumb).then(function () { return true; });
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonLocalIsTemplateStale(cacheKey, blobMeta) {
|
||||||
|
if (!cacheKey || cacheKey.indexOf('soondesign_template:') !== 0) return false;
|
||||||
|
var tplMeta = window._soonTemplateMeta;
|
||||||
|
if (!tplMeta || !tplMeta.updated_at) return false;
|
||||||
|
if (!blobMeta || !blobMeta.updatedAt) return false;
|
||||||
|
return String(tplMeta.updated_at) !== String(blobMeta.updatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.soonLocalOpen = soonLocalOpen;
|
||||||
|
window.soonLocalGet = soonLocalGet;
|
||||||
|
window.soonLocalPut = soonLocalPut;
|
||||||
|
window.soonLocalPutThumb = soonLocalPutThumb;
|
||||||
|
window.soonLocalGetThumb = soonLocalGetThumb;
|
||||||
|
window.soonLocalRemove = soonLocalRemove;
|
||||||
|
window.soonLocalRenameKey = soonLocalRenameKey;
|
||||||
|
window.soonLocalEvictLRU = soonLocalEvictLRU;
|
||||||
|
window.soonLocalCacheAndThumb = soonLocalCacheAndThumb;
|
||||||
|
window.soonLocalIsTemplateStale = soonLocalIsTemplateStale;
|
||||||
|
window.soonExtractThumbFromJson = soonExtractThumbFromJson;
|
||||||
|
|
||||||
|
var LS_DROP = ['soondesign_recent', 'soondesign_history', 'soondesign_recent_migrated'];
|
||||||
|
var SS_DROP = ['soondesign_open_file', 'soondesign_open_type', 'soondesign_open_meta', 'soondesign_pending_import'];
|
||||||
|
|
||||||
|
function dropStorageKeys(storage, prefix) {
|
||||||
|
if (!storage) return;
|
||||||
|
try {
|
||||||
|
var rm = [];
|
||||||
|
for (var i = 0; i < storage.length; i++) {
|
||||||
|
var k = storage.key(i);
|
||||||
|
if (k && k.indexOf(prefix) === 0) rm.push(k);
|
||||||
|
}
|
||||||
|
rm.forEach(function (k) { storage.removeItem(k); });
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonClearLocalDesignCache() {
|
||||||
|
try {
|
||||||
|
LS_DROP.forEach(function (k) { localStorage.removeItem(k); });
|
||||||
|
dropStorageKeys(localStorage, 'soondesign_session:');
|
||||||
|
SS_DROP.forEach(function (k) { sessionStorage.removeItem(k); });
|
||||||
|
dropStorageKeys(sessionStorage, 'soondesign_session:');
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
dbPromise = null;
|
||||||
|
if (typeof indexedDB === 'undefined') return Promise.resolve();
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
var done = function () { resolve(); };
|
||||||
|
try {
|
||||||
|
var req = indexedDB.deleteDatabase(DB_NAME);
|
||||||
|
req.onsuccess = req.onerror = req.onblocked = done;
|
||||||
|
} catch (e2) { done(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.soonClearLocalDesignCache = soonClearLocalDesignCache;
|
||||||
|
})();
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var RECENT_KEY = 'soondesign_recent';
|
||||||
|
var MIGRATED_KEY = 'soondesign_recent_migrated';
|
||||||
|
var HISTORY_KEY = 'soondesign_history';
|
||||||
|
var MAX_ITEMS = 20;
|
||||||
|
|
||||||
|
function nowTimeStr() {
|
||||||
|
var d = new Date();
|
||||||
|
function pad(n) { return n < 10 ? '0' + n : String(n); }
|
||||||
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' +
|
||||||
|
pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRaw() {
|
||||||
|
try {
|
||||||
|
var raw = localStorage.getItem(RECENT_KEY);
|
||||||
|
if (!raw) return { version: 1, items: [] };
|
||||||
|
var j = JSON.parse(raw);
|
||||||
|
if (!j || !Array.isArray(j.items)) return { version: 1, items: [] };
|
||||||
|
return j;
|
||||||
|
} catch (e) {
|
||||||
|
return { version: 1, items: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeRaw(data) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(RECENT_KEY, JSON.stringify(data));
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonRecentKindFromKey(key) {
|
||||||
|
if (!key) return 'local';
|
||||||
|
if (key.indexOf('soondesign_file:') === 0) return 'cloud';
|
||||||
|
if (key.indexOf('soondesign_template:') === 0) return 'template';
|
||||||
|
return 'local';
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFileId(key) {
|
||||||
|
var m = String(key).match(/^soondesign_file:(\d+)/);
|
||||||
|
return m ? parseInt(m[1], 10) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonRecentList() {
|
||||||
|
return readRaw().items.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonRecentRemove(key) {
|
||||||
|
if (!key) return;
|
||||||
|
var data = readRaw();
|
||||||
|
data.items = data.items.filter(function (it) { return it.key !== key; });
|
||||||
|
writeRaw(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonRecentUpsert(item) {
|
||||||
|
if (!item || !item.key) return;
|
||||||
|
var key = item.key;
|
||||||
|
var kind = item.kind || soonRecentKindFromKey(key);
|
||||||
|
var type = item.type != null ? item.type : 1;
|
||||||
|
var name = item.name || '';
|
||||||
|
if (!name && typeof window.soonDisplayFileName === 'function') {
|
||||||
|
name = window.soonDisplayFileName(key);
|
||||||
|
}
|
||||||
|
var entry = {
|
||||||
|
key: key,
|
||||||
|
name: name || 'design.soon',
|
||||||
|
type: type,
|
||||||
|
kind: kind,
|
||||||
|
fileId: item.fileId != null ? item.fileId : parseFileId(key),
|
||||||
|
thumbRef: item.thumbRef || key,
|
||||||
|
time: item.time || nowTimeStr()
|
||||||
|
};
|
||||||
|
var data = readRaw();
|
||||||
|
data.items = data.items.filter(function (it) { return it.key !== key; });
|
||||||
|
data.items.unshift(entry);
|
||||||
|
if (data.items.length > MAX_ITEMS) data.items = data.items.slice(0, MAX_ITEMS);
|
||||||
|
writeRaw(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonRecentUpsertFromOpen(fileKey, json) {
|
||||||
|
if (!fileKey) return;
|
||||||
|
var type = 1;
|
||||||
|
if (json) {
|
||||||
|
type = json.soonType ? json.soonType : (json.backBlackPic ? 2 : 1);
|
||||||
|
}
|
||||||
|
var name = '';
|
||||||
|
if (typeof window.soonDisplayFileName === 'function') name = window.soonDisplayFileName(fileKey);
|
||||||
|
var tplMeta = window._soonTemplateMeta;
|
||||||
|
if (tplMeta && tplMeta.name && fileKey.indexOf('soondesign_template:') === 0) {
|
||||||
|
name = tplMeta.name;
|
||||||
|
if (tplMeta.type) type = tplMeta.type;
|
||||||
|
}
|
||||||
|
var fileMeta = window._soonFileMeta;
|
||||||
|
if (fileMeta && fileMeta.name && fileKey.indexOf('soondesign_file:') === 0) {
|
||||||
|
name = fileMeta.name;
|
||||||
|
}
|
||||||
|
soonRecentUpsert({
|
||||||
|
key: fileKey,
|
||||||
|
name: name,
|
||||||
|
type: type,
|
||||||
|
kind: soonRecentKindFromKey(fileKey),
|
||||||
|
fileId: parseFileId(fileKey),
|
||||||
|
thumbRef: fileKey,
|
||||||
|
time: nowTimeStr(),
|
||||||
|
updated_at: (tplMeta && tplMeta.updated_at) ? tplMeta.updated_at : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonRecentMigrateFromHistory() {
|
||||||
|
try {
|
||||||
|
if (localStorage.getItem(MIGRATED_KEY) === '1') return;
|
||||||
|
} catch (e) { return; }
|
||||||
|
var histRaw;
|
||||||
|
try {
|
||||||
|
histRaw = localStorage.getItem(HISTORY_KEY);
|
||||||
|
} catch (e) { return; }
|
||||||
|
if (!histRaw) {
|
||||||
|
try { localStorage.setItem(MIGRATED_KEY, '1'); } catch (e2) { /* ignore */ }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var hist;
|
||||||
|
try {
|
||||||
|
hist = JSON.parse(histRaw);
|
||||||
|
} catch (e) {
|
||||||
|
try { localStorage.setItem(MIGRATED_KEY, '1'); } catch (e2) { /* ignore */ }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var list = (hist && Array.isArray(hist.history)) ? hist.history : [];
|
||||||
|
list.forEach(function (h) {
|
||||||
|
if (!h || !h.path) return;
|
||||||
|
soonRecentUpsert({
|
||||||
|
key: h.path,
|
||||||
|
name: typeof window.soonDisplayFileName === 'function' ? window.soonDisplayFileName(h.path) : h.path,
|
||||||
|
type: h.type || 1,
|
||||||
|
kind: soonRecentKindFromKey(h.path),
|
||||||
|
fileId: parseFileId(h.path),
|
||||||
|
thumbRef: h.path,
|
||||||
|
time: h.time || nowTimeStr()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
try { localStorage.setItem(MIGRATED_KEY, '1'); } catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonRecentEnrichFromCloud(items, cloudItems) {
|
||||||
|
if (!items || !items.length || !cloudItems || !cloudItems.length) return items;
|
||||||
|
var byId = {};
|
||||||
|
cloudItems.forEach(function (c) {
|
||||||
|
if (c && c.id != null) byId[c.id] = c;
|
||||||
|
});
|
||||||
|
return items.map(function (it) {
|
||||||
|
if (!it.fileId || !byId[it.fileId]) return it;
|
||||||
|
var c = byId[it.fileId];
|
||||||
|
var copy = Object.assign({}, it);
|
||||||
|
if (c.name) copy.name = c.name;
|
||||||
|
var curKey = it.filePath || it.key;
|
||||||
|
if (c.version != null && typeof window.soonMakeFileKey === 'function') {
|
||||||
|
var newKey = window.soonMakeFileKey(c.id, c.version);
|
||||||
|
if (newKey !== curKey) {
|
||||||
|
soonRecentRemove(curKey);
|
||||||
|
copy.filePath = newKey;
|
||||||
|
copy.key = newKey;
|
||||||
|
copy.thumbRef = newKey;
|
||||||
|
soonRecentUpsert({
|
||||||
|
key: newKey,
|
||||||
|
name: copy.name,
|
||||||
|
type: copy.type,
|
||||||
|
kind: soonRecentKindFromKey(newKey),
|
||||||
|
fileId: c.id,
|
||||||
|
thumbRef: newKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonRecentOnCloudSave(res, prevKey, type) {
|
||||||
|
if (!res || !res.fileKey) return;
|
||||||
|
var dropKeys = {};
|
||||||
|
if (prevKey && prevKey !== res.fileKey) dropKeys[prevKey] = 1;
|
||||||
|
if (res.fileId != null) {
|
||||||
|
readRaw().items.forEach(function (it) {
|
||||||
|
if (it.fileId === res.fileId && it.key !== res.fileKey) dropKeys[it.key] = 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Object.keys(dropKeys).forEach(function (k) {
|
||||||
|
soonRecentRemove(k);
|
||||||
|
if (typeof window.soonLocalRemove === 'function') window.soonLocalRemove(k);
|
||||||
|
});
|
||||||
|
var name = res.name || '';
|
||||||
|
if (!name && typeof window.soonDisplayFileName === 'function') {
|
||||||
|
name = window.soonDisplayFileName(res.fileKey);
|
||||||
|
}
|
||||||
|
soonRecentUpsert({
|
||||||
|
key: res.fileKey,
|
||||||
|
name: name || 'design.soon',
|
||||||
|
type: type != null ? type : 1,
|
||||||
|
kind: soonRecentKindFromKey(res.fileKey),
|
||||||
|
fileId: res.fileId,
|
||||||
|
thumbRef: res.fileKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.soonRecentList = soonRecentList;
|
||||||
|
window.soonRecentRemove = soonRecentRemove;
|
||||||
|
window.soonRecentUpsert = soonRecentUpsert;
|
||||||
|
window.soonRecentUpsertFromOpen = soonRecentUpsertFromOpen;
|
||||||
|
window.soonRecentMigrateFromHistory = soonRecentMigrateFromHistory;
|
||||||
|
window.soonRecentKindFromKey = soonRecentKindFromKey;
|
||||||
|
window.soonRecentEnrichFromCloud = soonRecentEnrichFromCloud;
|
||||||
|
window.soonRecentOnCloudSave = soonRecentOnCloudSave;
|
||||||
|
})();
|
||||||
@@ -150,6 +150,7 @@ function applySysLan(data) {
|
|||||||
localStorage.setItem("lang", "zh");
|
localStorage.setItem("lang", "zh");
|
||||||
break;
|
break;
|
||||||
case 'zh-TW':
|
case 'zh-TW':
|
||||||
|
case 'ozh':
|
||||||
s_lan = "ozh";
|
s_lan = "ozh";
|
||||||
// 同步到全局作用域(用于 .jsc 文件加载)
|
// 同步到全局作用域(用于 .jsc 文件加载)
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -375,51 +376,17 @@ window.rotate = function (id) {
|
|||||||
$(`#${id}`).attr("angle", angle);
|
$(`#${id}`).attr("angle", angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addBackground() {
|
function soonPlaceBgOnCanvas(image, canvasSelector) {
|
||||||
// 类型改变
|
var cw = $(canvasSelector).width() || (document.querySelector(canvasSelector) && document.querySelector(canvasSelector).clientWidth) || 800;
|
||||||
fabric.Image.fromURL(soonAsset('bg_front_1.png'), function (image) {
|
var ch = $(canvasSelector).height() || (document.querySelector(canvasSelector) && document.querySelector(canvasSelector).clientHeight) || 600;
|
||||||
let width = $("#canvas1").width()
|
image.left = (cw - image.width * image.get("scaleX")) / 2;
|
||||||
|
image.top = (ch - image.height * image.get("scaleY")) / 2;
|
||||||
image.left = ($("#canvas1").width() - image.width * image.get("scaleX")) / 2;
|
|
||||||
image.top = ($("#canvas1").height() - image.height * image.get("scaleY")) / 2;
|
|
||||||
|
|
||||||
image.selectable = false;
|
image.selectable = false;
|
||||||
image.hoverable = false;
|
image.hoverable = false;
|
||||||
image.hoverCursor = "default"
|
image.hoverCursor = "default";
|
||||||
image.setSrc(image.toDataURL(), function (image) {
|
}
|
||||||
image.scale(1, 1);
|
|
||||||
background_image1 = image;
|
|
||||||
background_image = background_image1;
|
|
||||||
window.background_image1 = background_image1;
|
|
||||||
window.background_image = background_image;
|
|
||||||
canvas.add(image);
|
|
||||||
canvas.renderAll();
|
|
||||||
//pre_objs1.push(JSON.stringify(objs1));
|
|
||||||
//pre_json1.push(canvas1.toJSON(["selectable","hoverable","hoverCursor"]));
|
|
||||||
|
|
||||||
recordObjs1.push(JSON.stringify(objs1));
|
|
||||||
recordJson1.push(canvas1.toJSON(TO_JSON_PROPERTIES));
|
|
||||||
});
|
|
||||||
// 类型改变
|
|
||||||
fabric.Image.fromURL(soonAsset('bg_back.png'), function (image) {
|
|
||||||
image.left = ($("#canvas2").width() - image.width) / 2;
|
|
||||||
image.top = ($("#canvas2").height() - image.height) / 2;
|
|
||||||
|
|
||||||
image.selectable = false;
|
|
||||||
image.hoverable = false;
|
|
||||||
image.hoverCursor = "default"
|
|
||||||
|
|
||||||
image.setSrc(image.toDataURL(), function (image) {
|
|
||||||
background_image2 = image;
|
|
||||||
window.background_image2 = background_image2;
|
|
||||||
canvas2.add(image);
|
|
||||||
canvas2.renderAll();
|
|
||||||
//pre_objs2.push(JSON.stringify(objs2));
|
|
||||||
//pre_json2.push(canvas2.toJSON(["selectable","hoverable","hoverCursor"]));
|
|
||||||
|
|
||||||
recordObjs2.push(JSON.stringify(objs2));
|
|
||||||
recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES));
|
|
||||||
|
|
||||||
|
function soonOpenPendingDesignFile() {
|
||||||
var file = typeof window.soonResolveOpenFileKey === 'function'
|
var file = typeof window.soonResolveOpenFileKey === 'function'
|
||||||
? window.soonResolveOpenFileKey(GetFile)
|
? window.soonResolveOpenFileKey(GetFile)
|
||||||
: (GetFile().get('file') || '');
|
: (GetFile().get('file') || '');
|
||||||
@@ -429,10 +396,53 @@ function addBackground() {
|
|||||||
}
|
}
|
||||||
window.openFile(file);
|
window.openFile(file);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
|
||||||
|
function addBackground() {
|
||||||
|
if (is_bgi_add || window.is_bgi_add) return;
|
||||||
|
var loadBg = typeof window.soonFabricImageFromAsset === 'function'
|
||||||
|
? window.soonFabricImageFromAsset
|
||||||
|
: null;
|
||||||
|
var whenReady = typeof window.soonRunWhenCanvasReady === 'function'
|
||||||
|
? window.soonRunWhenCanvasReady
|
||||||
|
: function (_sel, fn) { fn(); };
|
||||||
|
|
||||||
|
function finishBack(backImg) {
|
||||||
|
if (backImg) {
|
||||||
|
soonPlaceBgOnCanvas(backImg, '#canvas2');
|
||||||
|
background_image2 = backImg;
|
||||||
|
window.background_image2 = backImg;
|
||||||
|
canvas2.add(backImg);
|
||||||
|
canvas2.renderAll();
|
||||||
|
recordObjs2.push(JSON.stringify(objs2));
|
||||||
|
recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES));
|
||||||
|
}
|
||||||
is_bgi_add = true;
|
is_bgi_add = true;
|
||||||
|
window.is_bgi_add = true;
|
||||||
|
soonOpenPendingDesignFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLoad() {
|
||||||
|
if (!loadBg) return;
|
||||||
|
loadBg('bg_front_1.png', function (frontImg) {
|
||||||
|
if (!frontImg) return;
|
||||||
|
frontImg.scale(1, 1);
|
||||||
|
soonPlaceBgOnCanvas(frontImg, '#canvas1');
|
||||||
|
background_image1 = frontImg;
|
||||||
|
background_image = frontImg;
|
||||||
|
window.background_image1 = frontImg;
|
||||||
|
window.background_image = frontImg;
|
||||||
|
canvas.add(frontImg);
|
||||||
|
canvas.renderAll();
|
||||||
|
recordObjs1.push(JSON.stringify(objs1));
|
||||||
|
recordJson1.push(canvas1.toJSON(TO_JSON_PROPERTIES));
|
||||||
|
loadBg('bg_back.png', finishBack, function () { loadBg('bg_back.png', finishBack); });
|
||||||
|
}, function () {
|
||||||
|
setTimeout(startLoad, 200);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
whenReady('#canvas1', startLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将 updateControls 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
|
// 将 updateControls 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
|
||||||
|
|||||||
@@ -1181,6 +1181,7 @@ window.openFile = function open(file, jAlready) {
|
|||||||
recordJson2.push(j2);
|
recordJson2.push(j2);
|
||||||
}, 0);
|
}, 0);
|
||||||
if (typeof window.soonTryConsumeCloudImport === 'function') window.soonTryConsumeCloudImport(file);
|
if (typeof window.soonTryConsumeCloudImport === 'function') window.soonTryConsumeCloudImport(file);
|
||||||
|
if (typeof window.soonRecentUpsertFromOpen === 'function') window.soonRecentUpsertFromOpen(file, j);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1263,11 +1264,23 @@ function cloudSaveSuccessLabel(res, fallback) {
|
|||||||
return fallback || '';
|
return fallback || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function soonPageDesignType() {
|
||||||
|
try {
|
||||||
|
var p = new URLSearchParams(location.search);
|
||||||
|
return Number(p.get('type')) || 1;
|
||||||
|
} catch (e) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onCloudWriteDone(res, fallback, callback) {
|
function onCloudWriteDone(res, fallback, callback) {
|
||||||
applyCloudWriteResult(res, fallback);
|
applyCloudWriteResult(res, fallback);
|
||||||
if (res && res.fileKey && typeof window.soonClearPendingImport === 'function') {
|
if (res && res.fileKey && typeof window.soonClearPendingImport === 'function') {
|
||||||
window.soonClearPendingImport();
|
window.soonClearPendingImport();
|
||||||
}
|
}
|
||||||
|
if (typeof window.soonRecentOnCloudSave === 'function') {
|
||||||
|
window.soonRecentOnCloudSave(res, fallback, soonPageDesignType());
|
||||||
|
}
|
||||||
layer.msg(language_str("saveSucc") + cloudSaveSuccessLabel(res, fallback));
|
layer.msg(language_str("saveSucc") + cloudSaveSuccessLabel(res, fallback));
|
||||||
if (callback && typeof callback === 'function') callback();
|
if (callback && typeof callback === 'function') callback();
|
||||||
}
|
}
|
||||||
@@ -1276,15 +1289,6 @@ function guardWebSave() {
|
|||||||
return typeof window.soonGuardCloudSave === 'function' ? window.soonGuardCloudSave() : true;
|
return typeof window.soonGuardCloudSave === 'function' ? window.soonGuardCloudSave() : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reportSaveError(err) {
|
|
||||||
if (err && err.status) return;
|
|
||||||
if (typeof window.soonShowApiError === 'function') {
|
|
||||||
window.soonShowApiError({ status: err && err.status, message: (err && err.message) || '保存失败' });
|
|
||||||
} else if (typeof window.soonToast === 'function') {
|
|
||||||
window.soonToast('保存失败', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveAs(op1, callback) {
|
function saveAs(op1, callback) {
|
||||||
// 临时移除辅助线,保存后再恢复
|
// 临时移除辅助线,保存后再恢复
|
||||||
let guideLines1 = [];
|
let guideLines1 = [];
|
||||||
@@ -1319,26 +1323,26 @@ function saveAs(op1, callback) {
|
|||||||
let o = { f: j, b: {}, fo: objs1, bo: objs2 };
|
let o = { f: j, b: {}, fo: objs1, bo: objs2 };
|
||||||
let con_o = Object.assign(o, op1);
|
let con_o = Object.assign(o, op1);
|
||||||
if (!guardWebSave()) return;
|
if (!guardWebSave()) return;
|
||||||
|
var prevOpenKey = openAs.name || '';
|
||||||
|
if (typeof window.soonApplySavePreview === 'function') window.soonApplySavePreview(con_o, canvas1);
|
||||||
|
con_o.soonType = 1;
|
||||||
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
|
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
|
||||||
if (!dialogApi) return;
|
if (!dialogApi) return;
|
||||||
dialogApi.showSaveDialog({
|
dialogApi.showSaveDialog({
|
||||||
title: language_str("saveFile"),
|
title: language_str("saveFile"),
|
||||||
filters: [{ name: 'Soon File Type', extensions: ['soon'] }],
|
filters: [{ name: 'Soon File Type', extensions: ['soon'] }],
|
||||||
defaultPath: openAs.name || undefined
|
defaultPath: typeof window.soonSaveDialogDefaultPath === 'function' ? window.soonSaveDialogDefaultPath(openAs.name) : openAs.name
|
||||||
}).then(function(result) {
|
}).then(function(result) {
|
||||||
if (result.canceled) return;
|
if (result.canceled) return;
|
||||||
var fp = result.filePath || (result.fileHandle && result.fileHandle.name);
|
var fp = typeof window.soonEnsureSaveFileName === 'function'
|
||||||
|
? window.soonEnsureSaveFileName(result.filePath || (result.fileHandle && result.fileHandle.name))
|
||||||
|
: (result.filePath || (result.fileHandle && result.fileHandle.name));
|
||||||
if (!fp) return;
|
if (!fp) return;
|
||||||
var pathExt = (typeof window !== 'undefined' && window.path && window.path.extname) ? window.path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : '');
|
|
||||||
if (pathExt !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon';
|
|
||||||
var content = JSON.stringify(con_o);
|
var content = JSON.stringify(con_o);
|
||||||
con_o.soonType = 1;
|
if (typeof window.soonWriteSoonContentWeb === 'function' &&
|
||||||
if (window.platformBridge && window.platformBridge.writeFile) {
|
window.soonWriteSoonContentWeb(fp, content, prevOpenKey, function (res, pk) {
|
||||||
window.platformBridge.writeFile(fp, content).then(function (res) {
|
onCloudWriteDone(res, pk, callback);
|
||||||
onCloudWriteDone(res, fp, callback);
|
})) return;
|
||||||
}).catch(reportSaveError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
||||||
if (fs) {
|
if (fs) {
|
||||||
fs.writeFileSync(result.filePath || fp, content, 'utf8');
|
fs.writeFileSync(result.filePath || fp, content, 'utf8');
|
||||||
@@ -1347,7 +1351,9 @@ function saveAs(op1, callback) {
|
|||||||
saveHistory();
|
saveHistory();
|
||||||
if (callback && typeof callback === 'function') callback();
|
if (callback && typeof callback === 'function') callback();
|
||||||
}
|
}
|
||||||
}).catch(reportSaveError);
|
}).catch(function (err) {
|
||||||
|
if (typeof window.soonReportSaveError === 'function') window.soonReportSaveError(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(op1, callback) {
|
function save(op1, callback) {
|
||||||
@@ -1384,15 +1390,18 @@ function save(op1, callback) {
|
|||||||
let o = { f: j, b: {}, fo: objs1, bo: objs2 };
|
let o = { f: j, b: {}, fo: objs1, bo: objs2 };
|
||||||
let con_o = Object.assign(o, op1);
|
let con_o = Object.assign(o, op1);
|
||||||
if (!guardWebSave()) return;
|
if (!guardWebSave()) return;
|
||||||
if (openAs.name != "") {
|
var prevOpenKey = openAs.name || '';
|
||||||
|
if (typeof window.soonApplySavePreview === 'function') window.soonApplySavePreview(con_o, canvas1);
|
||||||
con_o.soonType = 1;
|
con_o.soonType = 1;
|
||||||
var content = JSON.stringify(con_o);
|
var content = JSON.stringify(con_o);
|
||||||
if (window.platformBridge && window.platformBridge.writeFile) {
|
var needsDialog = typeof window.soonNeedsSaveDialog === 'function'
|
||||||
window.platformBridge.writeFile(openAs.name, content).then(function (res) {
|
? window.soonNeedsSaveDialog(openAs.name)
|
||||||
onCloudWriteDone(res, openAs.name, callback);
|
: !openAs.name;
|
||||||
}).catch(reportSaveError);
|
if (openAs.name && !needsDialog) {
|
||||||
return;
|
if (typeof window.soonWriteSoonContentWeb === 'function' &&
|
||||||
}
|
window.soonWriteSoonContentWeb(openAs.name, content, prevOpenKey, function (res, pk) {
|
||||||
|
onCloudWriteDone(res, pk, callback);
|
||||||
|
})) return;
|
||||||
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
||||||
if (fs) {
|
if (fs) {
|
||||||
fs.writeFileSync(openAs.name, content, 'utf8');
|
fs.writeFileSync(openAs.name, content, 'utf8');
|
||||||
@@ -1407,21 +1416,17 @@ function save(op1, callback) {
|
|||||||
dialogApi.showSaveDialog({
|
dialogApi.showSaveDialog({
|
||||||
title: language_str("saveFile"),
|
title: language_str("saveFile"),
|
||||||
filters: [{ name: 'Soon File Type', extensions: ['soon'] }],
|
filters: [{ name: 'Soon File Type', extensions: ['soon'] }],
|
||||||
defaultPath: openAs.name || undefined
|
defaultPath: typeof window.soonSaveDialogDefaultPath === 'function' ? window.soonSaveDialogDefaultPath(openAs.name) : openAs.name
|
||||||
}).then(async function(result) {
|
}).then(function(result) {
|
||||||
if (result.canceled) return;
|
if (result.canceled) return;
|
||||||
var fp = result.filePath || (result.fileHandle && result.fileHandle.name);
|
var fp = typeof window.soonEnsureSaveFileName === 'function'
|
||||||
|
? window.soonEnsureSaveFileName(result.filePath || (result.fileHandle && result.fileHandle.name))
|
||||||
|
: (result.filePath || (result.fileHandle && result.fileHandle.name));
|
||||||
if (!fp) return;
|
if (!fp) return;
|
||||||
var pathExt = (typeof window !== 'undefined' && window.path && window.path.extname) ? window.path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : '');
|
if (typeof window.soonWriteSoonContentWeb === 'function' &&
|
||||||
if (pathExt !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon';
|
window.soonWriteSoonContentWeb(fp, content, prevOpenKey, function (res, pk) {
|
||||||
var content = JSON.stringify(con_o);
|
onCloudWriteDone(res, pk, callback);
|
||||||
con_o.soonType = 1;
|
})) return;
|
||||||
if (window.platformBridge && window.platformBridge.writeFile) {
|
|
||||||
window.platformBridge.writeFile(fp, content).then(function (res) {
|
|
||||||
onCloudWriteDone(res, fp, callback);
|
|
||||||
}).catch(reportSaveError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
||||||
if (fs) {
|
if (fs) {
|
||||||
fs.writeFileSync(result.filePath || fp, content, 'utf8');
|
fs.writeFileSync(result.filePath || fp, content, 'utf8');
|
||||||
@@ -1430,12 +1435,27 @@ function save(op1, callback) {
|
|||||||
saveHistory();
|
saveHistory();
|
||||||
if (callback && typeof callback === 'function') callback();
|
if (callback && typeof callback === 'function') callback();
|
||||||
}
|
}
|
||||||
}).catch(reportSaveError);
|
}).catch(function (err) {
|
||||||
|
if (typeof window.soonReportSaveError === 'function') window.soonReportSaveError(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.saveHistory = function saveHistory() {
|
window.saveHistory = function saveHistory() {
|
||||||
function doWrite(j) {
|
|
||||||
var currentPath = openAs.name;
|
var currentPath = openAs.name;
|
||||||
|
if (!currentPath) return;
|
||||||
|
if (window.platformBridge && window.fs == null) {
|
||||||
|
if (typeof window.soonRecentUpsert === 'function') {
|
||||||
|
window.soonRecentUpsert({
|
||||||
|
key: currentPath,
|
||||||
|
name: typeof window.soonDisplayFileName === 'function' ? window.soonDisplayFileName(currentPath) : currentPath,
|
||||||
|
type: soonPageDesignType(),
|
||||||
|
kind: typeof window.soonRecentKindFromKey === 'function' ? window.soonRecentKindFromKey(currentPath) : 'local',
|
||||||
|
thumbRef: currentPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function doWrite(j) {
|
||||||
if (!currentPath) return;
|
if (!currentPath) return;
|
||||||
var pathForHistory = currentPath;
|
var pathForHistory = currentPath;
|
||||||
if (window.platformBridge && currentPath.indexOf('soondesign_session:') !== 0 && currentPath.indexOf('soondesign_file:') !== 0) {
|
if (window.platformBridge && currentPath.indexOf('soondesign_session:') !== 0 && currentPath.indexOf('soondesign_file:') !== 0) {
|
||||||
|
|||||||
+164
-55
@@ -9,12 +9,27 @@ function soonDesign2MainHeight() {
|
|||||||
return Math.max(480, window.innerHeight - (tb ? tb.offsetHeight : 0));
|
return Math.max(480, window.innerHeight - (tb ? tb.offsetHeight : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function soonDesign2SidebarWidth() {
|
||||||
|
var colRight = document.querySelector('body.soon-design-page .col-right');
|
||||||
|
if (colRight && colRight.offsetWidth > 0) return colRight.offsetWidth;
|
||||||
|
return 320;
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonDesign2ToolboxWidth() {
|
||||||
|
var toolbox = document.querySelector('body.soon-design-page .container .left');
|
||||||
|
if (toolbox && toolbox.offsetWidth > 0) return toolbox.offsetWidth;
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
|
||||||
function soonDesign2CanvasWidth() {
|
function soonDesign2CanvasWidth() {
|
||||||
var w = $('#canvas-div').width();
|
var canvasDiv = document.getElementById('canvas-div');
|
||||||
if (w > 0) return w;
|
if (canvasDiv && canvasDiv.clientWidth > 0) return canvasDiv.clientWidth;
|
||||||
w = $('.col-left').width();
|
var colLeft = document.querySelector('body.soon-design-page .col-left');
|
||||||
if (w > 0) return Math.max(320, w - 70);
|
if (colLeft && colLeft.clientWidth > 0) {
|
||||||
return Math.max(320, window.innerWidth - 390);
|
return Math.max(320, colLeft.clientWidth - soonDesign2ToolboxWidth());
|
||||||
|
}
|
||||||
|
var docW = document.documentElement.clientWidth || window.innerWidth;
|
||||||
|
return Math.max(320, docW - soonDesign2SidebarWidth() - soonDesign2ToolboxWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
function layoutDesign2Shell() {
|
function layoutDesign2Shell() {
|
||||||
@@ -80,6 +95,7 @@ zoom = (function () {
|
|||||||
var z = divW / 1200 > 1 ? 1 : divW / 1200;
|
var z = divW / 1200 > 1 ? 1 : divW / 1200;
|
||||||
return z > 0 ? z : 0.5;
|
return z > 0 ? z : 0.5;
|
||||||
})()
|
})()
|
||||||
|
window.zoom = zoom;
|
||||||
|
|
||||||
// 初始化标尺
|
// 初始化标尺
|
||||||
function initRulers() {
|
function initRulers() {
|
||||||
@@ -518,29 +534,110 @@ function initRulers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
initRulers();
|
function relayoutDesign2Canvas() {
|
||||||
if (window.SOON_DEPLOY_CONFIG && ($('#canvas-div').width() || 0) < 100) {
|
|
||||||
requestAnimationFrame(function () {
|
|
||||||
requestAnimationFrame(function () {
|
|
||||||
var layout = layoutDesign2Shell();
|
var layout = layoutDesign2Shell();
|
||||||
var divW = layout.canvasW;
|
var divW = layout.canvasW;
|
||||||
zoom = divW / 1200 > 1 ? 1 : divW / 1200;
|
zoom = divW / 1200 > 1 ? 1 : divW / 1200;
|
||||||
if (zoom <= 0) zoom = 0.5;
|
if (zoom <= 0) zoom = 0.5;
|
||||||
|
window.zoom = zoom;
|
||||||
canvas1.setWidth(layout.canvasW);
|
canvas1.setWidth(layout.canvasW);
|
||||||
canvas1.setHeight(layout.canvasH);
|
canvas1.setHeight(layout.canvasH);
|
||||||
canvas2.setWidth(layout.canvasW);
|
canvas2.setWidth(layout.canvasW);
|
||||||
canvas2.setHeight(layout.canvasH);
|
canvas2.setHeight(layout.canvasH);
|
||||||
canvas1.zoomToPoint(new fabric.Point(canvas1.width / 2, canvas1.height / 2), zoom);
|
canvas1.zoomToPoint(new fabric.Point(canvas1.width / 2, canvas1.height / 2), zoom);
|
||||||
canvas2.zoomToPoint(new fabric.Point(canvas2.width / 2, canvas2.height / 2), zoom);
|
canvas2.zoomToPoint(new fabric.Point(canvas2.width / 2, canvas2.height / 2), zoom);
|
||||||
window.zoom = zoom;
|
|
||||||
canvas1.renderAll();
|
canvas1.renderAll();
|
||||||
canvas2.renderAll();
|
canvas2.renderAll();
|
||||||
initRulers();
|
initRulers();
|
||||||
});
|
if (typeof window.soonDesign2ScaleBgSources === 'function') {
|
||||||
});
|
window.soonDesign2ScaleBgSources();
|
||||||
|
}
|
||||||
|
if (window.is_bgi_add) {
|
||||||
|
canvas1.requestRenderAll();
|
||||||
|
canvas2.requestRenderAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$('#source_front').width(zoom * $('#source_front').width())
|
|
||||||
$('#source_back').width(zoom * $('#source_back').width())
|
function soonDesign2ScaleBgSources() {
|
||||||
|
var z = zoom || window.zoom || 0.5;
|
||||||
|
var sf = document.getElementById('source_front');
|
||||||
|
var sb = document.getElementById('source_back');
|
||||||
|
if (sf && sf.naturalWidth > 0) {
|
||||||
|
sf.style.width = (sf.naturalWidth * z) + 'px';
|
||||||
|
sf.style.height = (sf.naturalHeight * z) + 'px';
|
||||||
|
}
|
||||||
|
if (sb && sb.naturalWidth > 0) {
|
||||||
|
sb.style.width = (sb.naturalWidth * z) + 'px';
|
||||||
|
sb.style.height = (sb.naturalHeight * z) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonDesign2BgOverlayMetrics(imgEl) {
|
||||||
|
if (!imgEl || !imgEl.complete || imgEl.naturalWidth <= 0) return null;
|
||||||
|
var z = zoom || window.zoom || 0.5;
|
||||||
|
var w = parseFloat(imgEl.style.width);
|
||||||
|
if (!w || w <= 0) w = imgEl.naturalWidth * z;
|
||||||
|
var h = parseFloat(imgEl.style.height);
|
||||||
|
if (!h || h <= 0) h = imgEl.naturalHeight * z;
|
||||||
|
return { w: w, h: h };
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonDesign2PrepareBgSources(done) {
|
||||||
|
var sf = document.getElementById('source_front');
|
||||||
|
var sb = document.getElementById('source_back');
|
||||||
|
if (!sf || !sb) {
|
||||||
|
is_bgi_add = true;
|
||||||
|
window.is_bgi_add = true;
|
||||||
|
if (typeof done === 'function') done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var ver = typeof bg_version !== 'undefined' ? bg_version : 1;
|
||||||
|
var frontName = 'front_bg' + ver + '_2.png';
|
||||||
|
if (!(sf.complete && sf.naturalWidth > 0 && (sf.src || '').indexOf(frontName) !== -1)) {
|
||||||
|
sf.src = soonAsset(frontName);
|
||||||
|
}
|
||||||
|
var pending = 2;
|
||||||
|
function finishOne() {
|
||||||
|
pending--;
|
||||||
|
if (pending > 0) return;
|
||||||
|
soonDesign2ScaleBgSources();
|
||||||
|
is_bgi_add = true;
|
||||||
|
window.is_bgi_add = true;
|
||||||
|
canvas1.requestRenderAll();
|
||||||
|
canvas2.requestRenderAll();
|
||||||
|
if (typeof done === 'function') done();
|
||||||
|
}
|
||||||
|
function watch(img) {
|
||||||
|
if (img.complete && img.naturalWidth > 0) {
|
||||||
|
finishOne();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
img.onload = finishOne;
|
||||||
|
img.onerror = finishOne;
|
||||||
|
}
|
||||||
|
watch(sf);
|
||||||
|
watch(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonDesign2StartBackground() {
|
||||||
|
soonDesign2PrepareBgSources(addBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonDesign2Kickoff() {
|
||||||
|
if (window._soonDesign2KickoffDone) return;
|
||||||
|
window._soonDesign2KickoffDone = true;
|
||||||
|
if (window.SOON_DEPLOY_CONFIG && ($('#canvas-div').width() || 0) < 100) {
|
||||||
|
relayoutDesign2Canvas();
|
||||||
|
}
|
||||||
|
soonDesign2StartBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.soonDesign2ScaleBgSources = soonDesign2ScaleBgSources;
|
||||||
|
window.soonDesign2PrepareBgSources = soonDesign2PrepareBgSources;
|
||||||
|
window.soonDesign2BgOverlayMetrics = soonDesign2BgOverlayMetrics;
|
||||||
|
window.soonDesign2Kickoff = soonDesign2Kickoff;
|
||||||
|
|
||||||
|
initRulers();
|
||||||
$('.canvas-container:eq(1)').hide()
|
$('.canvas-container:eq(1)').hide()
|
||||||
canvas = canvas1
|
canvas = canvas1
|
||||||
window.canvas = canvas
|
window.canvas = canvas
|
||||||
@@ -1643,45 +1740,17 @@ function getCoordsMaxY(acoords) {
|
|||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addBackground() {
|
function soonPlaceBgOnCanvas(image, canvasSelector) {
|
||||||
// 类型改变
|
var cw = $(canvasSelector).width() || (document.querySelector(canvasSelector) && document.querySelector(canvasSelector).clientWidth) || 800
|
||||||
fabric.Image.fromURL(soonAsset('bg_front_2.png'), function (image) {
|
var ch = $(canvasSelector).height() || (document.querySelector(canvasSelector) && document.querySelector(canvasSelector).clientHeight) || 600
|
||||||
image.left = ($('#canvas1').width() - image.width * image.get('scaleX')) / 2
|
image.left = (cw - image.width * image.get('scaleX')) / 2
|
||||||
image.top = ($('#canvas1').height() - image.height * image.get('scaleY')) / 2
|
image.top = (ch - image.height * image.get('scaleY')) / 2
|
||||||
|
|
||||||
image.selectable = false
|
image.selectable = false
|
||||||
image.hoverable = false
|
image.hoverable = false
|
||||||
image.hoverCursor = 'default'
|
image.hoverCursor = 'default'
|
||||||
image.setSrc(image.toDataURL(), function (image) {
|
}
|
||||||
background_image1 = image
|
|
||||||
background_image = background_image1
|
|
||||||
window.background_image1 = background_image1;
|
|
||||||
window.background_image = background_image;
|
|
||||||
canvas1.add(image)
|
|
||||||
canvas1.renderAll()
|
|
||||||
|
|
||||||
recordObjs1.push(JSON.stringify(objs1))
|
|
||||||
const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped'];
|
|
||||||
recordJson1.push(canvas1.toJSON(props))
|
|
||||||
})
|
|
||||||
// 类型改变
|
|
||||||
fabric.Image.fromURL(soonAsset('bg_back_2.png'), function (image) {
|
|
||||||
image.left = ($('#canvas2').width() - image.width) / 2
|
|
||||||
image.top = ($('#canvas2').height() - image.height) / 2
|
|
||||||
|
|
||||||
image.selectable = false
|
|
||||||
image.hoverable = false
|
|
||||||
image.hoverCursor = 'default'
|
|
||||||
|
|
||||||
image.setSrc(image.toDataURL(), function (image) {
|
|
||||||
background_image2 = image
|
|
||||||
window.background_image2 = background_image2;
|
|
||||||
canvas2.add(image)
|
|
||||||
canvas2.renderAll()
|
|
||||||
|
|
||||||
recordObjs2.push(JSON.stringify(objs2))
|
|
||||||
recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES))
|
|
||||||
|
|
||||||
|
function soonOpenPendingDesignFile() {
|
||||||
var file = typeof window.soonResolveOpenFileKey === 'function'
|
var file = typeof window.soonResolveOpenFileKey === 'function'
|
||||||
? window.soonResolveOpenFileKey(GetFile)
|
? window.soonResolveOpenFileKey(GetFile)
|
||||||
: (GetFile().get('file') || '');
|
: (GetFile().get('file') || '');
|
||||||
@@ -1691,10 +1760,51 @@ function addBackground() {
|
|||||||
}
|
}
|
||||||
window.openFile(file);
|
window.openFile(file);
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
})
|
|
||||||
is_bgi_add = true
|
function addBackground() {
|
||||||
})
|
if (background_image1 && background_image2) return;
|
||||||
|
var loadBg = typeof window.soonFabricImageFromAsset === 'function'
|
||||||
|
? window.soonFabricImageFromAsset
|
||||||
|
: null;
|
||||||
|
var whenReady = typeof window.soonRunWhenCanvasReady === 'function'
|
||||||
|
? window.soonRunWhenCanvasReady
|
||||||
|
: function (_sel, fn) { fn(); };
|
||||||
|
var props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped'];
|
||||||
|
|
||||||
|
function finishBack(backImg) {
|
||||||
|
if (backImg) {
|
||||||
|
soonPlaceBgOnCanvas(backImg, '#canvas2');
|
||||||
|
background_image2 = backImg;
|
||||||
|
window.background_image2 = backImg;
|
||||||
|
canvas2.add(backImg);
|
||||||
|
canvas2.renderAll();
|
||||||
|
recordObjs2.push(JSON.stringify(objs2));
|
||||||
|
recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES));
|
||||||
|
}
|
||||||
|
soonOpenPendingDesignFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLoad() {
|
||||||
|
if (!loadBg) return;
|
||||||
|
loadBg('bg_front_2.png', function (frontImg) {
|
||||||
|
if (!frontImg) return;
|
||||||
|
soonPlaceBgOnCanvas(frontImg, '#canvas1');
|
||||||
|
background_image1 = frontImg;
|
||||||
|
background_image = frontImg;
|
||||||
|
window.background_image1 = frontImg;
|
||||||
|
window.background_image = frontImg;
|
||||||
|
canvas1.add(frontImg);
|
||||||
|
canvas1.renderAll();
|
||||||
|
recordObjs1.push(JSON.stringify(objs1));
|
||||||
|
recordJson1.push(canvas1.toJSON(props));
|
||||||
|
loadBg('bg_back_2.png', finishBack, function () { loadBg('bg_back_2.png', finishBack, null, true); }, true);
|
||||||
|
}, function () {
|
||||||
|
setTimeout(startLoad, 200);
|
||||||
|
}, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
whenReady('#canvas1', startLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPic(pointer) {
|
function addPic(pointer) {
|
||||||
@@ -1975,6 +2085,7 @@ function applySysLan(data) {
|
|||||||
localStorage.setItem('lang', 'zh')
|
localStorage.setItem('lang', 'zh')
|
||||||
break
|
break
|
||||||
case 'zh-TW':
|
case 'zh-TW':
|
||||||
|
case 'ozh':
|
||||||
s_lan = 'ozh'
|
s_lan = 'ozh'
|
||||||
// 同步到全局作用域(用于 .jsc 文件加载)
|
// 同步到全局作用域(用于 .jsc 文件加载)
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -2112,8 +2223,6 @@ if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
|
|||||||
window.platformBridge.onClose(onCloseConfirm)
|
window.platformBridge.onClose(onCloseConfirm)
|
||||||
}
|
}
|
||||||
|
|
||||||
addBackground() //添加背景 白色卡片
|
|
||||||
|
|
||||||
initAligningGuidelines(canvas1)
|
initAligningGuidelines(canvas1)
|
||||||
initAligningGuidelines(canvas2)
|
initAligningGuidelines(canvas2)
|
||||||
|
|
||||||
|
|||||||
@@ -1086,11 +1086,23 @@ function cloudSaveSuccessLabel(res, fallback) {
|
|||||||
return fallback || '';
|
return fallback || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function soonPageDesignType() {
|
||||||
|
try {
|
||||||
|
var p = new URLSearchParams(location.search);
|
||||||
|
return Number(p.get('type')) || 2;
|
||||||
|
} catch (e) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onCloudWriteDone(res, fallback, callback) {
|
function onCloudWriteDone(res, fallback, callback) {
|
||||||
applyCloudWriteResult(res, fallback);
|
applyCloudWriteResult(res, fallback);
|
||||||
if (res && res.fileKey && typeof window.soonClearPendingImport === 'function') {
|
if (res && res.fileKey && typeof window.soonClearPendingImport === 'function') {
|
||||||
window.soonClearPendingImport();
|
window.soonClearPendingImport();
|
||||||
}
|
}
|
||||||
|
if (typeof window.soonRecentOnCloudSave === 'function') {
|
||||||
|
window.soonRecentOnCloudSave(res, fallback, soonPageDesignType());
|
||||||
|
}
|
||||||
layer.msg(language_str('saveSucc') + cloudSaveSuccessLabel(res, fallback));
|
layer.msg(language_str('saveSucc') + cloudSaveSuccessLabel(res, fallback));
|
||||||
if (callback && typeof callback === 'function') callback();
|
if (callback && typeof callback === 'function') callback();
|
||||||
}
|
}
|
||||||
@@ -1099,15 +1111,6 @@ function guardWebSave() {
|
|||||||
return typeof window.soonGuardCloudSave === 'function' ? window.soonGuardCloudSave() : true;
|
return typeof window.soonGuardCloudSave === 'function' ? window.soonGuardCloudSave() : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reportSaveError(err) {
|
|
||||||
if (err && err.status) return;
|
|
||||||
if (typeof window.soonShowApiError === 'function') {
|
|
||||||
window.soonShowApiError({ status: err && err.status, message: (err && err.message) || '保存失败' });
|
|
||||||
} else if (typeof window.soonToast === 'function') {
|
|
||||||
window.soonToast('保存失败', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveAs(op1, callback) {
|
function saveAs(op1, callback) {
|
||||||
// 临时移除辅助线,保存后再恢复
|
// 临时移除辅助线,保存后再恢复
|
||||||
let guideLines1 = []
|
let guideLines1 = []
|
||||||
@@ -1142,23 +1145,23 @@ function saveAs(op1, callback) {
|
|||||||
let o = { f: j, b: b, fo: objs1, bo: objs2 }
|
let o = { f: j, b: b, fo: objs1, bo: objs2 }
|
||||||
let con_o = Object.assign(o, op1)
|
let con_o = Object.assign(o, op1)
|
||||||
if (!guardWebSave()) return;
|
if (!guardWebSave()) return;
|
||||||
|
var prevOpenKey = openAs.name || '';
|
||||||
|
if (typeof window.soonApplySavePreview === 'function') window.soonApplySavePreview(con_o, canvas1);
|
||||||
|
con_o.soonType = 2;
|
||||||
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
|
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
|
||||||
if (!dialogApi) return;
|
if (!dialogApi) return;
|
||||||
dialogApi.showSaveDialog({ title: language_str('saveFile'), filters: [{ name: 'Soon File Type', extensions: ['soon'] }], defaultPath: openAs.name || undefined })
|
dialogApi.showSaveDialog({ title: language_str('saveFile'), filters: [{ name: 'Soon File Type', extensions: ['soon'] }], defaultPath: typeof window.soonSaveDialogDefaultPath === 'function' ? window.soonSaveDialogDefaultPath(openAs.name) : openAs.name })
|
||||||
.then(function(result) {
|
.then(function(result) {
|
||||||
if (result.canceled) return;
|
if (result.canceled) return;
|
||||||
var fp = result.filePath || (result.fileHandle && result.fileHandle.name);
|
var fp = typeof window.soonEnsureSaveFileName === 'function'
|
||||||
|
? window.soonEnsureSaveFileName(result.filePath || (result.fileHandle && result.fileHandle.name))
|
||||||
|
: (result.filePath || (result.fileHandle && result.fileHandle.name));
|
||||||
if (!fp) return;
|
if (!fp) return;
|
||||||
var ext = (typeof path !== 'undefined' && path && path.extname) ? path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : '');
|
|
||||||
if (ext !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon';
|
|
||||||
con_o.soonType = 2;
|
|
||||||
var content = JSON.stringify(con_o);
|
var content = JSON.stringify(con_o);
|
||||||
if (window.platformBridge && window.platformBridge.writeFile) {
|
if (typeof window.soonWriteSoonContentWeb === 'function' &&
|
||||||
window.platformBridge.writeFile(fp, content).then(function (res) {
|
window.soonWriteSoonContentWeb(fp, content, prevOpenKey, function (res, pk) {
|
||||||
onCloudWriteDone(res, fp, callback);
|
onCloudWriteDone(res, pk, callback);
|
||||||
}).catch(reportSaveError);
|
})) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
||||||
if (fs) {
|
if (fs) {
|
||||||
fs.writeFileSync(fp, content, 'utf8');
|
fs.writeFileSync(fp, content, 'utf8');
|
||||||
@@ -1168,7 +1171,9 @@ function saveAs(op1, callback) {
|
|||||||
if (callback && typeof callback === 'function') callback();
|
if (callback && typeof callback === 'function') callback();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(reportSaveError);
|
.catch(function (err) {
|
||||||
|
if (typeof window.soonReportSaveError === 'function') window.soonReportSaveError(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(op1, callback) {
|
function save(op1, callback) {
|
||||||
@@ -1204,16 +1209,19 @@ function save(op1, callback) {
|
|||||||
|
|
||||||
let o = { f: j, b: b, fo: objs1, bo: objs2 }
|
let o = { f: j, b: b, fo: objs1, bo: objs2 }
|
||||||
let con_o = Object.assign(o, op1)
|
let con_o = Object.assign(o, op1)
|
||||||
con_o.soonType = 2
|
|
||||||
var content = JSON.stringify(con_o)
|
|
||||||
if (!guardWebSave()) return;
|
if (!guardWebSave()) return;
|
||||||
if (openAs.name != '') {
|
var prevOpenKey = openAs.name || '';
|
||||||
if (window.platformBridge && window.platformBridge.writeFile) {
|
if (typeof window.soonApplySavePreview === 'function') window.soonApplySavePreview(con_o, canvas1);
|
||||||
window.platformBridge.writeFile(openAs.name, content).then(function (res) {
|
con_o.soonType = 2;
|
||||||
onCloudWriteDone(res, openAs.name, callback);
|
var content = JSON.stringify(con_o);
|
||||||
}).catch(reportSaveError);
|
var needsDialog = typeof window.soonNeedsSaveDialog === 'function'
|
||||||
return;
|
? window.soonNeedsSaveDialog(openAs.name)
|
||||||
}
|
: !openAs.name;
|
||||||
|
if (openAs.name && !needsDialog) {
|
||||||
|
if (typeof window.soonWriteSoonContentWeb === 'function' &&
|
||||||
|
window.soonWriteSoonContentWeb(openAs.name, content, prevOpenKey, function (res, pk) {
|
||||||
|
onCloudWriteDone(res, pk, callback);
|
||||||
|
})) return;
|
||||||
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
||||||
if (fs) {
|
if (fs) {
|
||||||
fs.writeFileSync(openAs.name, content, 'utf8');
|
fs.writeFileSync(openAs.name, content, 'utf8');
|
||||||
@@ -1225,19 +1233,17 @@ function save(op1, callback) {
|
|||||||
}
|
}
|
||||||
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
|
var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null);
|
||||||
if (!dialogApi) return;
|
if (!dialogApi) return;
|
||||||
dialogApi.showSaveDialog({ title: language_str('saveFile'), filters: [{ name: 'Soon File Type', extensions: ['soon'] }], defaultPath: openAs.name || undefined })
|
dialogApi.showSaveDialog({ title: language_str('saveFile'), filters: [{ name: 'Soon File Type', extensions: ['soon'] }], defaultPath: typeof window.soonSaveDialogDefaultPath === 'function' ? window.soonSaveDialogDefaultPath(openAs.name) : openAs.name })
|
||||||
.then(function(result) {
|
.then(function(result) {
|
||||||
if (result.canceled) return;
|
if (result.canceled) return;
|
||||||
var fp = result.filePath || (result.fileHandle && result.fileHandle.name);
|
var fp = typeof window.soonEnsureSaveFileName === 'function'
|
||||||
|
? window.soonEnsureSaveFileName(result.filePath || (result.fileHandle && result.fileHandle.name))
|
||||||
|
: (result.filePath || (result.fileHandle && result.fileHandle.name));
|
||||||
if (!fp) return;
|
if (!fp) return;
|
||||||
var ext = (typeof path !== 'undefined' && path && path.extname) ? path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : '');
|
if (typeof window.soonWriteSoonContentWeb === 'function' &&
|
||||||
if (ext !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon';
|
window.soonWriteSoonContentWeb(fp, content, prevOpenKey, function (res, pk) {
|
||||||
if (window.platformBridge && window.platformBridge.writeFile) {
|
onCloudWriteDone(res, pk, callback);
|
||||||
window.platformBridge.writeFile(fp, content).then(function (res) {
|
})) return;
|
||||||
onCloudWriteDone(res, fp, callback);
|
|
||||||
}).catch(reportSaveError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
||||||
if (fs) {
|
if (fs) {
|
||||||
fs.writeFileSync(fp, content, 'utf8');
|
fs.writeFileSync(fp, content, 'utf8');
|
||||||
@@ -1247,12 +1253,27 @@ function save(op1, callback) {
|
|||||||
if (callback && typeof callback === 'function') callback();
|
if (callback && typeof callback === 'function') callback();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(reportSaveError);
|
.catch(function (err) {
|
||||||
|
if (typeof window.soonReportSaveError === 'function') window.soonReportSaveError(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.saveHistory = function saveHistory() {
|
window.saveHistory = function saveHistory() {
|
||||||
function doWrite(j) {
|
|
||||||
var currentPath = openAs.name;
|
var currentPath = openAs.name;
|
||||||
|
if (!currentPath) return;
|
||||||
|
if (window.platformBridge && window.fs == null) {
|
||||||
|
if (typeof window.soonRecentUpsert === 'function') {
|
||||||
|
window.soonRecentUpsert({
|
||||||
|
key: currentPath,
|
||||||
|
name: typeof window.soonDisplayFileName === 'function' ? window.soonDisplayFileName(currentPath) : currentPath,
|
||||||
|
type: soonPageDesignType(),
|
||||||
|
kind: typeof window.soonRecentKindFromKey === 'function' ? window.soonRecentKindFromKey(currentPath) : 'local',
|
||||||
|
thumbRef: currentPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function doWrite(j) {
|
||||||
if (!currentPath) return;
|
if (!currentPath) return;
|
||||||
var pathForHistory = currentPath;
|
var pathForHistory = currentPath;
|
||||||
if (window.platformBridge && currentPath.indexOf('soondesign_session:') !== 0 && currentPath.indexOf('soondesign_file:') !== 0) {
|
if (window.platformBridge && currentPath.indexOf('soondesign_session:') !== 0 && currentPath.indexOf('soondesign_file:') !== 0) {
|
||||||
@@ -1455,6 +1476,7 @@ window.openFile = function open(file, jAlready) {
|
|||||||
step = step2
|
step = step2
|
||||||
}
|
}
|
||||||
if (typeof window.soonTryConsumeCloudImport === 'function') window.soonTryConsumeCloudImport(file);
|
if (typeof window.soonTryConsumeCloudImport === 'function') window.soonTryConsumeCloudImport(file);
|
||||||
|
if (typeof window.soonRecentUpsertFromOpen === 'function') window.soonRecentUpsertFromOpen(file, j);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas1.loadFromJSON(j.f, function () {
|
canvas1.loadFromJSON(j.f, function () {
|
||||||
|
|||||||
@@ -359,9 +359,17 @@ $('#version_change').click(function () {
|
|||||||
} else {
|
} else {
|
||||||
bg_version = 1
|
bg_version = 1
|
||||||
}
|
}
|
||||||
// 类型改变
|
var sf = document.getElementById('source_front')
|
||||||
$('#source_front').attr('src', soonAsset('front_bg') + bg_version + '_2.png')
|
if (!sf) return
|
||||||
canvas.renderAll()
|
sf.onload = function () {
|
||||||
|
if (typeof window.soonDesign2ScaleBgSources === 'function') {
|
||||||
|
window.soonDesign2ScaleBgSources()
|
||||||
|
}
|
||||||
|
canvas1.requestRenderAll()
|
||||||
|
canvas2.requestRenderAll()
|
||||||
|
}
|
||||||
|
sf.onerror = sf.onload
|
||||||
|
sf.src = soonAsset('front_bg' + bg_version + '_2.png')
|
||||||
})
|
})
|
||||||
$('#front_side').click(function () {
|
$('#front_side').click(function () {
|
||||||
$(this).addClass('ui-button-active')
|
$(this).addClass('ui-button-active')
|
||||||
@@ -1373,58 +1381,6 @@ $('#new').click(function () {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
function chagneTitle() { }
|
function chagneTitle() { }
|
||||||
function addBackground() {
|
|
||||||
// 类型改变
|
|
||||||
fabric.Image.fromURL(soonAsset('bg_front_2.png'), function (image) {
|
|
||||||
//image.set("scaleX",$("#canvas-div").width() / 1200 < 1 ? $("#canvas-div").width() / 1200 : 1)
|
|
||||||
|
|
||||||
image.left = ($('#canvas1').width() - image.width * image.get('scaleX')) / 2
|
|
||||||
image.top = ($('#canvas1').height() - image.height * image.get('scaleY')) / 2
|
|
||||||
|
|
||||||
image.selectable = false
|
|
||||||
image.hoverable = false
|
|
||||||
image.hoverCursor = 'default'
|
|
||||||
image.setSrc(image.toDataURL(), function (image) {
|
|
||||||
background_image1 = image
|
|
||||||
background_image = background_image1
|
|
||||||
canvas.add(image)
|
|
||||||
canvas.renderAll()
|
|
||||||
//pre_objs1.push(JSON.stringify(objs1));
|
|
||||||
|
|
||||||
recordObjs1.push(JSON.stringify(objs1))
|
|
||||||
const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped'];
|
|
||||||
recordJson1.push(canvas1.toJSON(props))
|
|
||||||
})
|
|
||||||
// 类型改变
|
|
||||||
fabric.Image.fromURL(soonAsset('bg_back_2.png'), function (image) {
|
|
||||||
image.left = ($('#canvas2').width() - image.width) / 2
|
|
||||||
image.top = ($('#canvas2').height() - image.height) / 2
|
|
||||||
|
|
||||||
image.selectable = false
|
|
||||||
image.hoverable = false
|
|
||||||
image.hoverCursor = 'default'
|
|
||||||
|
|
||||||
image.setSrc(image.toDataURL(), function (image) {
|
|
||||||
background_image2 = image
|
|
||||||
canvas2.add(image)
|
|
||||||
canvas2.renderAll()
|
|
||||||
//pre_objs2.push(JSON.stringify(objs2));
|
|
||||||
|
|
||||||
recordObjs2.push(JSON.stringify(objs2))
|
|
||||||
const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped'];
|
|
||||||
recordJson2.push(canvas2.toJSON(props))
|
|
||||||
|
|
||||||
let file = GetFile().get('file')
|
|
||||||
if (file && file != 'empty') {
|
|
||||||
if (typeof window.openFile === 'function') {
|
|
||||||
window.openFile(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
is_bgi_add = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function selectObject(target, index) {
|
function selectObject(target, index) {
|
||||||
updateControls()
|
updateControls()
|
||||||
|
|
||||||
@@ -2749,10 +2705,19 @@ if (typeof window._design2CanvasEventsInitialized === 'undefined') {
|
|||||||
|
|
||||||
// 保留 canvas1 特定的 after:render 处理
|
// 保留 canvas1 特定的 after:render 处理
|
||||||
canvas1.on('after:render', function () {
|
canvas1.on('after:render', function () {
|
||||||
if (is_bgi_add) {
|
if (!window.is_bgi_add) return
|
||||||
var image = document.getElementById('source_front')
|
var image = document.getElementById('source_front')
|
||||||
ctx1.drawImage(image, (canvas.width - $('#source_front').width()) / 2, (canvas.height - $('#source_front').height()) / 2, $('#source_front').width(), $('#source_front').height())
|
var metrics = typeof window.soonDesign2BgOverlayMetrics === 'function'
|
||||||
}
|
? window.soonDesign2BgOverlayMetrics(image)
|
||||||
|
: null
|
||||||
|
if (!metrics) return
|
||||||
|
ctx1.drawImage(
|
||||||
|
image,
|
||||||
|
(canvas1.width - metrics.w) / 2,
|
||||||
|
(canvas1.height - metrics.h) / 2,
|
||||||
|
metrics.w,
|
||||||
|
metrics.h
|
||||||
|
)
|
||||||
})
|
})
|
||||||
// 保留 canvas1 特定的 text:changed 处理
|
// 保留 canvas1 特定的 text:changed 处理
|
||||||
canvas1.on('text:changed', (e) => {
|
canvas1.on('text:changed', (e) => {
|
||||||
@@ -2760,11 +2725,27 @@ if (typeof window._design2CanvasEventsInitialized === 'undefined') {
|
|||||||
})
|
})
|
||||||
// 保留 canvas2 特定的 after:render 处理
|
// 保留 canvas2 特定的 after:render 处理
|
||||||
canvas2.on('after:render', function () {
|
canvas2.on('after:render', function () {
|
||||||
if (is_bgi_add) {
|
if (!window.is_bgi_add) return
|
||||||
var image = document.getElementById('source_back')
|
var image = document.getElementById('source_back')
|
||||||
ctx2.drawImage(image, (canvas.width - $('#source_back').width()) / 2, (canvas1.height - $('#source_back').height()) / 2, $('#source_back').width(), $('#source_back').height())
|
var metrics = typeof window.soonDesign2BgOverlayMetrics === 'function'
|
||||||
}
|
? window.soonDesign2BgOverlayMetrics(image)
|
||||||
|
: null
|
||||||
|
if (!metrics) return
|
||||||
|
ctx2.drawImage(
|
||||||
|
image,
|
||||||
|
(canvas2.width - metrics.w) / 2,
|
||||||
|
(canvas2.height - metrics.h) / 2,
|
||||||
|
metrics.w,
|
||||||
|
metrics.h
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
if (typeof window.soonDesign2Kickoff === 'function') {
|
||||||
|
window.soonDesign2Kickoff();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
// 注意:以下事件已在 core.js 的 initCanvasEvents() 中统一处理,移除重复监听以避免冲突:
|
// 注意:以下事件已在 core.js 的 initCanvasEvents() 中统一处理,移除重复监听以避免冲突:
|
||||||
// - mouse:down (通过 handleObjectSelected 处理)
|
// - mouse:down (通过 handleObjectSelected 处理)
|
||||||
// - mouse:up (已在 core.js 中处理)
|
// - mouse:up (已在 core.js 中处理)
|
||||||
|
|||||||
+148
-225
@@ -144,40 +144,18 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
var lang = localStorage.getItem("lang");
|
window.soonResolveLocale(function (code) {
|
||||||
|
|
||||||
if (lang) {
|
s_lan = code;
|
||||||
|
|
||||||
s_lan = lang;
|
|
||||||
|
|
||||||
langua_ge(s_lan);
|
langua_ge(s_lan);
|
||||||
|
|
||||||
$("#language_select").val(s_lan);
|
$("#language_select").val(s_lan);
|
||||||
|
|
||||||
} else if (window.platformBridge && window.platformBridge.getLocale) {
|
try { localStorage.setItem("lang", s_lan); } catch (e) {}
|
||||||
|
|
||||||
window.platformBridge.getLocale().then(function (loc) {
|
|
||||||
|
|
||||||
if (loc && loc.indexOf('zh') === 0) {
|
|
||||||
|
|
||||||
s_lan = loc.indexOf('TW') >= 0 ? 'ozh' : 'zh';
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
s_lan = 'en';
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
langua_ge(s_lan);
|
|
||||||
|
|
||||||
$("#language_select").val(s_lan);
|
|
||||||
|
|
||||||
localStorage.setItem("lang", s_lan);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$('#language_select').on('change', function () {
|
$('#language_select').on('change', function () {
|
||||||
@@ -199,19 +177,20 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
try { return !!localStorage.getItem('soon_access'); } catch (e) { return false; }
|
try { return !!localStorage.getItem('soon_access'); } catch (e) { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeFileKey(id, version) {
|
|
||||||
return typeof window.soonMakeFileKey === 'function'
|
|
||||||
? window.soonMakeFileKey(id, version)
|
|
||||||
: ('soondesign_file:' + id + ':v' + version);
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileListState = { page: 1, size: 12, total: 0, items: [] };
|
var fileListState = { page: 1, size: 12, total: 0, items: [] };
|
||||||
var templateListState = { page: 1, size: 8, total: 0, items: [] };
|
var templateListState = { page: 1, size: 8, total: 0, items: [] };
|
||||||
|
|
||||||
function displayNameFromPath(path) {
|
function displayNameFromPath(path) {
|
||||||
if (!path) return 'design.soon';
|
if (!path) return 'design.soon';
|
||||||
|
if (typeof window.soonDisplayFileName === 'function') {
|
||||||
|
var fromHelper = window.soonDisplayFileName(path);
|
||||||
|
if (fromHelper) return fromHelper;
|
||||||
|
}
|
||||||
var sessionPrefix = 'soondesign_session:';
|
var sessionPrefix = 'soondesign_session:';
|
||||||
if (path.indexOf(sessionPrefix) === 0) return path.substring(sessionPrefix.length);
|
if (path.indexOf(sessionPrefix) === 0) {
|
||||||
|
var slug = path.substring(sessionPrefix.length).replace(/-\d{10,}$/, '');
|
||||||
|
return /\.soon$/i.test(slug) ? slug : (slug + '.soon');
|
||||||
|
}
|
||||||
if (path.indexOf('soondesign_file:') === 0) {
|
if (path.indexOf('soondesign_file:') === 0) {
|
||||||
var meta = window._soonFileMeta;
|
var meta = window._soonFileMeta;
|
||||||
if (meta && meta.name) return meta.name;
|
if (meta && meta.name) return meta.name;
|
||||||
@@ -220,74 +199,6 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
return get_filename(path) || 'design.soon';
|
return get_filename(path) || 'design.soon';
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItemTime(value) {
|
|
||||||
if (!value) return 0;
|
|
||||||
var d = new Date(String(value).replace(' ', 'T'));
|
|
||||||
return isNaN(d.getTime()) ? 0 : d.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeRecentItems(localHistory, cloudItems) {
|
|
||||||
var items = [];
|
|
||||||
var cloudById = {};
|
|
||||||
var seenCloudIds = {};
|
|
||||||
(cloudItems || []).forEach(function (c) {
|
|
||||||
if (c && c.id != null) cloudById[c.id] = c;
|
|
||||||
});
|
|
||||||
|
|
||||||
(localHistory || []).forEach(function (h) {
|
|
||||||
if (!h || !h.path) return;
|
|
||||||
var cloudMatch = String(h.path).match(/^soondesign_file:(\d+)/);
|
|
||||||
if (cloudMatch) {
|
|
||||||
var id = parseInt(cloudMatch[1], 10);
|
|
||||||
var cloud = cloudById[id];
|
|
||||||
if (cloud) {
|
|
||||||
seenCloudIds[id] = true;
|
|
||||||
items.push({
|
|
||||||
kind: 'cloud',
|
|
||||||
filePath: makeFileKey(cloud.id, cloud.version),
|
|
||||||
fileId: cloud.id,
|
|
||||||
name: cloud.name || ('文件 #' + cloud.id),
|
|
||||||
type: h.type,
|
|
||||||
sortTime: parseItemTime(cloud.updated_at || cloud.created_at || h.time),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
items.push({
|
|
||||||
kind: 'local',
|
|
||||||
filePath: h.path,
|
|
||||||
name: displayNameFromPath(h.path),
|
|
||||||
type: h.type,
|
|
||||||
sortTime: parseItemTime(h.time),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
(cloudItems || []).forEach(function (c) {
|
|
||||||
if (!c || c.id == null || seenCloudIds[c.id]) return;
|
|
||||||
items.push({
|
|
||||||
kind: 'cloud',
|
|
||||||
filePath: makeFileKey(c.id, c.version),
|
|
||||||
fileId: c.id,
|
|
||||||
name: c.name || ('文件 #' + c.id),
|
|
||||||
type: null,
|
|
||||||
sortTime: parseItemTime(c.updated_at || c.created_at),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
items.sort(function (a, b) { return (b.sortTime || 0) - (a.sortTime || 0); });
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchLocalHistory() {
|
|
||||||
if (!window.sysAPI || typeof window.sysAPI.readHistory !== 'function') return [];
|
|
||||||
try {
|
|
||||||
var j = await window.sysAPI.readHistory();
|
|
||||||
return (j && Array.isArray(j.history)) ? j.history : [];
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchCloudItems() {
|
async function fetchCloudItems() {
|
||||||
if (!hasCloudAuth() || !window.platformBridge || !window.platformBridge.listCloudFiles) return [];
|
if (!hasCloudAuth() || !window.platformBridge || !window.platformBridge.listCloudFiles) return [];
|
||||||
try {
|
try {
|
||||||
@@ -299,15 +210,60 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function removeLocalHistoryPath(path) {
|
function removeLocalHistoryPath(path) {
|
||||||
if (!path || !window.sysAPI || typeof window.sysAPI.readHistory !== 'function') {
|
if (!path) return Promise.resolve();
|
||||||
|
if (typeof window.soonRecentRemove === 'function') window.soonRecentRemove(path);
|
||||||
|
if (typeof window.soonLocalRemove === 'function') {
|
||||||
|
return window.soonLocalRemove(path);
|
||||||
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return window.sysAPI.readHistory().then(function (j) {
|
|
||||||
var list = (j && Array.isArray(j.history)) ? j.history : [];
|
function recentFallbackThumb(type) {
|
||||||
var next = list.filter(function (item) { return item.path !== path; });
|
var t = Number(type) || 1;
|
||||||
if (next.length === list.length) return null;
|
return soonAsset(t === 2 ? 'bg_2.png' : 'bg_1.png');
|
||||||
return window.sysAPI.writeHistory({ history: next });
|
}
|
||||||
}).catch(function () { return null; });
|
|
||||||
|
async function resolveRecentThumb(item) {
|
||||||
|
var realType = item.type || 1;
|
||||||
|
var fallback = recentFallbackThumb(realType);
|
||||||
|
var thumbKey = item.thumbRef || item.filePath || item.key;
|
||||||
|
if (typeof window.soonLocalGetThumb === 'function' && thumbKey) {
|
||||||
|
var localThumb = await window.soonLocalGetThumb(thumbKey);
|
||||||
|
if (localThumb) {
|
||||||
|
return typeof soonSafeImageUrl === 'function'
|
||||||
|
? soonSafeImageUrl(localThumb, fallback)
|
||||||
|
: localThumb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.kind === 'template' && item.filePath) {
|
||||||
|
var tplMatch = String(item.filePath).match(/^soondesign_template:(\d+)/);
|
||||||
|
if (tplMatch) {
|
||||||
|
var base = (window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base) || '';
|
||||||
|
if (base) {
|
||||||
|
var v = item.updated_at ? ('?v=' + encodeURIComponent(item.updated_at)) : '';
|
||||||
|
return base + '/templates/' + tplMatch[1] + '/thumb' + v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.kind === 'cloud' && item.fileId && hasCloudAuth()) {
|
||||||
|
var apiBase = (window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base) || '';
|
||||||
|
if (apiBase) return apiBase + '/files/' + item.fileId + '/thumb';
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function recentDownloadBtnHtml() {
|
||||||
|
return '<button type="button" class="soon-file-card__action soon-file-card__download" title="下载 .soon" aria-label="下载">'
|
||||||
|
+ '<svg class="soon-file-card__icon" viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">'
|
||||||
|
+ '<path d="M12 4v10m0 0l3.5-3.5M12 14l-3.5-3.5M5 20h14"/></svg></button>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function recentDeleteBtnHtml() {
|
||||||
|
return '<button type="button" class="soon-file-card__action soon-file-card__delete" title="' + escapeAttr(language_str('deleteBtn')) + '" aria-label="' + escapeAttr(language_str('deleteBtn')) + '">'
|
||||||
|
+ '<svg class="soon-file-card__icon" viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">'
|
||||||
|
+ '<path d="M4 7h16"/><path d="M7 7l1.1 11.1a.9.9 0 00.9.9h6a.9.9 0 00.9-.9L17 7"/>'
|
||||||
|
+ '<path d="M9.5 7V5.6A1.6 1.6 0 0111.1 4h1.8a1.6 1.6 0 011.6 1.6V7"/>'
|
||||||
|
+ '<path d="M10 11v5.5"/><path d="M14 11v5.5"/></svg></button>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFilePager() {
|
function renderFilePager() {
|
||||||
@@ -344,22 +300,48 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadHistory() {
|
var _recentLoadedAt = 0;
|
||||||
|
|
||||||
|
async function loadHistory(force) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
if (!force && _recentLoadedAt && (Date.now() - _recentLoadedAt) < 60000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window.soonRecentMigrateFromHistory === 'function') {
|
||||||
|
window.soonRecentMigrateFromHistory();
|
||||||
|
}
|
||||||
|
|
||||||
var recentCountEl = document.getElementById('recentCount');
|
var recentCountEl = document.getElementById('recentCount');
|
||||||
var localHistory = await fetchLocalHistory();
|
var items = typeof window.soonRecentList === 'function' ? window.soonRecentList() : [];
|
||||||
|
var mapped = items.map(function (it) {
|
||||||
|
return {
|
||||||
|
kind: it.kind || (typeof window.soonRecentKindFromKey === 'function' ? window.soonRecentKindFromKey(it.key) : 'local'),
|
||||||
|
filePath: it.key,
|
||||||
|
fileId: it.fileId,
|
||||||
|
name: it.name,
|
||||||
|
type: it.type,
|
||||||
|
thumbRef: it.thumbRef || it.key,
|
||||||
|
updated_at: it.updated_at || ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasCloudAuth() && typeof window.soonRecentEnrichFromCloud === 'function') {
|
||||||
var cloudItems = await fetchCloudItems();
|
var cloudItems = await fetchCloudItems();
|
||||||
var merged = mergeRecentItems(localHistory, cloudItems);
|
mapped = window.soonRecentEnrichFromCloud(mapped, cloudItems);
|
||||||
fileListState.items = merged;
|
}
|
||||||
fileListState.total = merged.length;
|
|
||||||
|
fileListState.items = mapped;
|
||||||
|
fileListState.total = mapped.length;
|
||||||
var pages = Math.max(1, Math.ceil(fileListState.total / fileListState.size));
|
var pages = Math.max(1, Math.ceil(fileListState.total / fileListState.size));
|
||||||
if (fileListState.page > pages) fileListState.page = pages;
|
if (fileListState.page > pages) fileListState.page = pages;
|
||||||
|
_recentLoadedAt = Date.now();
|
||||||
|
|
||||||
if (recentCountEl) recentCountEl.textContent = String(fileListState.total);
|
if (recentCountEl) recentCountEl.textContent = String(fileListState.total);
|
||||||
|
|
||||||
if (!merged.length) {
|
if (!mapped.length) {
|
||||||
|
|
||||||
$(".card-list").html(soonEmptyBlock('暂无文件', '点击「打开文件」导入,或新建模板开始设计'));
|
$(".card-list").html(soonEmptyBlock('暂无文件', '点击「打开文件」导入,或新建模板开始设计'));
|
||||||
renderFilePager();
|
renderFilePager();
|
||||||
@@ -368,89 +350,30 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var start = (fileListState.page - 1) * fileListState.size;
|
var start = (fileListState.page - 1) * fileListState.size;
|
||||||
var slice = merged.slice(start, start + fileListState.size);
|
var slice = mapped.slice(start, start + fileListState.size);
|
||||||
let h = "";
|
let h = "";
|
||||||
|
|
||||||
for (let item of slice) {
|
for (let item of slice) {
|
||||||
|
|
||||||
let filePath = item.filePath;
|
let filePath = item.filePath;
|
||||||
let src = "";
|
|
||||||
let fileExists = true;
|
|
||||||
let imgStyle = "";
|
|
||||||
let realType = item.type || 1;
|
let realType = item.type || 1;
|
||||||
const soonData = await window.sysAPI.readJsonFile(filePath);
|
let src = await resolveRecentThumb(item);
|
||||||
|
let fallbackSrc = recentFallbackThumb(realType);
|
||||||
if (soonData) {
|
|
||||||
|
|
||||||
realType = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1);
|
|
||||||
|
|
||||||
var fallbackThumb = (realType == 2 || realType == "2") ? soonAsset('bg_2.png') : soonAsset('bg_1.png');
|
|
||||||
|
|
||||||
src = typeof soonSafeImageUrl === 'function'
|
|
||||||
|
|
||||||
? soonSafeImageUrl(soonData.frontDisplayPic, fallbackThumb)
|
|
||||||
|
|
||||||
: (soonData.frontDisplayPic || fallbackThumb);
|
|
||||||
|
|
||||||
if (!src) src = fallbackThumb;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
fileExists = false;
|
|
||||||
|
|
||||||
src = soonAsset((realType == 2 || realType == "2") ? 'bg_2.png' : 'bg_1.png');
|
|
||||||
|
|
||||||
imgStyle = "opacity: 0.6; filter: grayscale(100%);";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
let displayName = item.name || displayNameFromPath(filePath);
|
let displayName = item.name || displayNameFromPath(filePath);
|
||||||
|
|
||||||
let cardTitle = displayName;
|
let cardTitle = displayName;
|
||||||
|
|
||||||
var cardClass = 'card';
|
var cardClass = 'card';
|
||||||
|
|
||||||
if (!fileExists) {
|
var actions = recentDownloadBtnHtml() + recentDeleteBtnHtml();
|
||||||
|
|
||||||
cardTitle = language_str("clickToDelete");
|
h += `<div class="${cardClass}" data-file="${escapeAttr(filePath)}" data-kind="${item.kind}" data-type="${realType}"${item.fileId ? (' data-id="' + item.fileId + '"') : ''} data-name="${escapeAttr(displayName)}" title="${escapeAttr(cardTitle)}">
|
||||||
|
|
||||||
cardClass += ' soon-card--lost';
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var lostBadge = !fileExists
|
|
||||||
|
|
||||||
? ' <span class="soon-badge soon-badge--disabled">' + language_str("lost") + '</span>'
|
|
||||||
|
|
||||||
: '';
|
|
||||||
|
|
||||||
var actions = '';
|
|
||||||
if (item.kind === 'cloud' && item.fileId) {
|
|
||||||
actions = '<button type="button" class="soon-file-card__action soon-file-card__download" title="下载 .soon" aria-label="下载">'
|
|
||||||
+ '<svg class="soon-file-card__icon" viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">'
|
|
||||||
+ '<path d="M12 4v10m0 0l3.5-3.5M12 14l-3.5-3.5M5 20h14"/></svg></button>'
|
|
||||||
+ '<button type="button" class="soon-file-card__action soon-file-card__delete" title="' + escapeAttr(language_str('deleteBtn')) + '" aria-label="' + escapeAttr(language_str('deleteBtn')) + '">'
|
|
||||||
+ '<svg class="soon-file-card__icon" viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">'
|
|
||||||
+ '<path d="M4 7h16"/><path d="M7 7l1.1 11.1a.9.9 0 00.9.9h6a.9.9 0 00.9-.9L17 7"/>'
|
|
||||||
+ '<path d="M9.5 7V5.6A1.6 1.6 0 0111.1 4h1.8a1.6 1.6 0 011.6 1.6V7"/>'
|
|
||||||
+ '<path d="M10 11v5.5"/><path d="M14 11v5.5"/></svg></button>';
|
|
||||||
} else {
|
|
||||||
actions = '<button type="button" class="soon-file-card__action soon-file-card__delete" title="' + escapeAttr(language_str('deleteBtn')) + '" aria-label="' + escapeAttr(language_str('deleteBtn')) + '">'
|
|
||||||
+ '<svg class="soon-file-card__icon" viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">'
|
|
||||||
+ '<path d="M4 7h16"/><path d="M7 7l1.1 11.1a.9.9 0 00.9.9h6a.9.9 0 00.9-.9L17 7"/>'
|
|
||||||
+ '<path d="M9.5 7V5.6A1.6 1.6 0 0111.1 4h1.8a1.6 1.6 0 011.6 1.6V7"/>'
|
|
||||||
+ '<path d="M10 11v5.5"/><path d="M14 11v5.5"/></svg></button>';
|
|
||||||
}
|
|
||||||
|
|
||||||
h += `<div class="${cardClass}" data-file="${escapeAttr(filePath)}" data-kind="${item.kind}"${item.fileId ? (' data-id="' + item.fileId + '"') : ''} data-name="${escapeAttr(displayName)}" title="${escapeAttr(cardTitle)}">
|
|
||||||
|
|
||||||
<div class="rect ${realType == 2 ? 'rect1' : ''}">
|
<div class="rect ${realType == 2 ? 'rect1' : ''}">
|
||||||
|
|
||||||
<div class="soon-file-card__actions">${actions}</div>
|
<div class="soon-file-card__actions">${actions}</div>
|
||||||
|
|
||||||
<img src="${escapeAttr(src)}" style="${imgStyle}" alt="">
|
<img src="${escapeAttr(src)}" alt="" onerror="this.onerror=null;this.src='${escapeAttr(fallbackSrc)}'">
|
||||||
|
|
||||||
<div class="tip">${escapeAttr(displayName)}${lostBadge}</div>
|
<div class="tip">${escapeAttr(displayName)}</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -468,17 +391,13 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
$(".card-list").html(soonEmptyBlock('加载失败', '请刷新页面重试',
|
$(".card-list").html(soonEmptyBlock('加载失败', '请刷新页面重试',
|
||||||
'<button type="button" class="soon-btn soon-btn--sm soon-empty__action" id="fileListRetry">重试</button>'));
|
'<button type="button" class="soon-btn soon-btn--sm soon-empty__action" id="fileListRetry">重试</button>'));
|
||||||
var retry = document.getElementById('fileListRetry');
|
var retry = document.getElementById('fileListRetry');
|
||||||
if (retry) retry.onclick = function () { loadHistory(); };
|
if (retry) retry.onclick = function () { loadHistory(true); };
|
||||||
if (document.getElementById('filePager')) document.getElementById('filePager').innerHTML = '';
|
if (document.getElementById('filePager')) document.getElementById('filePager').innerHTML = '';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.soonReloadRecentFiles = loadHistory;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function renderTemplatesError(message) {
|
function renderTemplatesError(message) {
|
||||||
|
|
||||||
var grid = document.getElementById('templatesGrid');
|
var grid = document.getElementById('templatesGrid');
|
||||||
@@ -517,6 +436,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
id: id,
|
id: id,
|
||||||
name: m.name || m.title || '模板',
|
name: m.name || m.title || '模板',
|
||||||
type: type,
|
type: type,
|
||||||
|
updated_at: m.updated_at || '',
|
||||||
thumbSrc: id ? (base + '/templates/' + id + '/thumb' + v) : templateFallbackThumb(type)
|
thumbSrc: id ? (base + '/templates/' + id + '/thumb' + v) : templateFallbackThumb(type)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -528,6 +448,12 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
if (typeof window.soonToast === 'function') window.soonToast('模板不可用', 'error');
|
if (typeof window.soonToast === 'function') window.soonToast('模板不可用', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
window._soonTemplateMeta = {
|
||||||
|
id: id,
|
||||||
|
name: m.name || m.title || '模板',
|
||||||
|
type: type,
|
||||||
|
updated_at: m.updated_at || ''
|
||||||
|
};
|
||||||
var templateKey = typeof window.soonMakeTemplateKey === 'function'
|
var templateKey = typeof window.soonMakeTemplateKey === 'function'
|
||||||
? window.soonMakeTemplateKey(id)
|
? window.soonMakeTemplateKey(id)
|
||||||
: ('soondesign_template:' + id);
|
: ('soondesign_template:' + id);
|
||||||
@@ -658,16 +584,23 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
loadHistory();
|
loadHistory(true);
|
||||||
|
|
||||||
loadTemplates();
|
loadTemplates();
|
||||||
|
|
||||||
|
window.soonReloadRecentFiles = function () { loadHistory(true); };
|
||||||
window.soonReloadTemplates = function () { loadTemplates(true); };
|
window.soonReloadTemplates = function () { loadTemplates(true); };
|
||||||
document.addEventListener('visibilitychange', function () {
|
document.addEventListener('visibilitychange', function () {
|
||||||
if (document.visibilityState === 'visible') loadTemplates(false);
|
if (document.visibilityState === 'visible') {
|
||||||
|
loadTemplates(false);
|
||||||
|
loadHistory(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener('pageshow', function (e) {
|
window.addEventListener('pageshow', function (e) {
|
||||||
if (e.persisted) loadTemplates(false);
|
if (e.persisted) {
|
||||||
|
loadTemplates(false);
|
||||||
|
loadHistory(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var templatePrevNav = document.getElementById('templatePrev');
|
var templatePrevNav = document.getElementById('templatePrev');
|
||||||
@@ -713,6 +646,14 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
await window.platformBridge.deleteCloudFile(fileId);
|
await window.platformBridge.deleteCloudFile(fileId);
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
if (typeof window.soonRecentList === 'function') {
|
||||||
|
window.soonRecentList().forEach(function (it) {
|
||||||
|
if (it.fileId === fileId) {
|
||||||
|
if (typeof window.soonRecentRemove === 'function') window.soonRecentRemove(it.key);
|
||||||
|
if (typeof window.soonLocalRemove === 'function') window.soonLocalRemove(it.key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
layer.msg(language_str("deleted"), { icon: 1, time: 1000 });
|
layer.msg(language_str("deleted"), { icon: 1, time: 1000 });
|
||||||
if (typeof onDone === 'function') onDone();
|
if (typeof onDone === 'function') onDone();
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
@@ -726,14 +667,27 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
|
|
||||||
var card = $(this).closest('.card');
|
var card = $(this).closest('.card');
|
||||||
|
|
||||||
var id = parseInt(card.attr('data-id'), 10);
|
var filePath = card.attr('data-file') || '';
|
||||||
|
|
||||||
|
var kind = card.attr('data-kind') || 'local';
|
||||||
|
|
||||||
|
var fileId = parseInt(card.attr('data-id'), 10);
|
||||||
|
|
||||||
var name = card.attr('data-name') || 'design.soon';
|
var name = card.attr('data-name') || 'design.soon';
|
||||||
|
|
||||||
if (id && window.platformBridge && window.platformBridge.downloadCloudFile) {
|
var type = parseInt(card.attr('data-type'), 10) || 1;
|
||||||
|
|
||||||
window.platformBridge.downloadCloudFile(id, name).catch(function () {});
|
var item = {
|
||||||
|
kind: kind,
|
||||||
|
filePath: filePath,
|
||||||
|
key: filePath,
|
||||||
|
fileId: fileId || undefined,
|
||||||
|
name: name,
|
||||||
|
type: type
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof window.soonDownloadRecentItem === 'function') {
|
||||||
|
window.soonDownloadRecentItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -771,7 +725,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
} catch (err) { /* ignore */ }
|
} catch (err) { /* ignore */ }
|
||||||
}
|
}
|
||||||
layer.msg(language_str("deleted"), { icon: 1, time: 1000 });
|
layer.msg(language_str("deleted"), { icon: 1, time: 1000 });
|
||||||
loadHistory();
|
loadHistory(true);
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -781,19 +735,14 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmDeleteCloudFile(fileId, language_str('deleteFileConfirm'), loadHistory);
|
confirmDeleteCloudFile(fileId, language_str('deleteFileConfirm'), function () { loadHistory(true); });
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".card-list").on("click", '.card', async function () {
|
$(".card-list").on("click", '.card', function () {
|
||||||
|
|
||||||
let filePath = $(this).attr("data-file") || $(this).attr("data");
|
let filePath = $(this).attr("data-file") || $(this).attr("data");
|
||||||
|
let type = Number($(this).attr("data-type")) || 1;
|
||||||
const soonData = await window.sysAPI.readJsonFile(filePath);
|
|
||||||
|
|
||||||
if (soonData) {
|
|
||||||
|
|
||||||
let type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1);
|
|
||||||
|
|
||||||
if (typeof window.soonBindFileMeta === 'function') {
|
if (typeof window.soonBindFileMeta === 'function') {
|
||||||
window.soonBindFileMeta(filePath, { name: $(this).attr('data-name') || '' });
|
window.soonBindFileMeta(filePath, { name: $(this).attr('data-name') || '' });
|
||||||
@@ -801,32 +750,6 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
|||||||
|
|
||||||
openDesign(type, filePath);
|
openDesign(type, filePath);
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
var kind = $(this).attr('data-kind') || 'cloud';
|
|
||||||
|
|
||||||
var fileId = parseInt($(this).attr('data-id'), 10);
|
|
||||||
|
|
||||||
if (kind === 'local' || !fileId) {
|
|
||||||
|
|
||||||
removeLocalHistoryPath(filePath).then(function () {
|
|
||||||
if (filePath.indexOf('soondesign_session:') === 0) {
|
|
||||||
try {
|
|
||||||
sessionStorage.removeItem(filePath);
|
|
||||||
localStorage.removeItem(filePath);
|
|
||||||
} catch (err) { /* ignore */ }
|
|
||||||
}
|
|
||||||
loadHistory();
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmDeleteCloudFile(fileId, language_str("delContent"), loadHistory);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+185
-41
@@ -186,7 +186,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function downloadCloudFile(id, fileName) {
|
function downloadCloudFile(id, fileName) {
|
||||||
if (!requireCloudAuth('下载')) return Promise.reject(new Error('unauthorized'));
|
if (!getAccessToken()) {
|
||||||
|
return Promise.reject(new Error('unauthorized'));
|
||||||
|
}
|
||||||
return authedFetch('files/' + id + '/download', { headers: { Accept: 'application/octet-stream' } })
|
return authedFetch('files/' + id + '/download', { headers: { Accept: 'application/octet-stream' } })
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -202,6 +204,52 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSoonSaveDialog(options) {
|
||||||
|
if (options && options.soonFile === true) return true;
|
||||||
|
if (options && options.soonFile === false) return false;
|
||||||
|
var raw = (options && options.defaultPath) ? String(options.defaultPath) : '';
|
||||||
|
if (!raw) return true;
|
||||||
|
if (typeof window.soonIsTemplateKey === 'function' && window.soonIsTemplateKey(raw)) return true;
|
||||||
|
if (raw.indexOf('soondesign_file:') === 0 || raw.indexOf('soondesign_session:') === 0) return true;
|
||||||
|
var filters = options && options.filters;
|
||||||
|
if (filters && Array.isArray(filters)) {
|
||||||
|
for (var i = 0; i < filters.length; i++) {
|
||||||
|
var exts = filters[i].extensions;
|
||||||
|
if (exts && exts.indexOf('soon') >= 0) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /\.soon$/i.test(raw.split(/[/\\]/).pop() || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function genericSaveDialogName(raw) {
|
||||||
|
var n = String(raw || 'file').trim();
|
||||||
|
return n.split(/[/\\]/).pop() || n || 'file';
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSaveDialogName(name, options, isSoon) {
|
||||||
|
var n = String(name || '').trim();
|
||||||
|
if (!n) n = isSoon ? 'design.soon' : 'file';
|
||||||
|
if (isSoon) {
|
||||||
|
return typeof window.soonEnsureSoonExt === 'function'
|
||||||
|
? window.soonEnsureSoonExt(n)
|
||||||
|
: normalizeSoonName(n);
|
||||||
|
}
|
||||||
|
return genericSaveDialogName(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultSoonSaveDialogName(raw) {
|
||||||
|
if (!raw || (typeof window.soonIsTemplateKey === 'function' && window.soonIsTemplateKey(raw))) {
|
||||||
|
return typeof window.soonDefaultNewSoonName === 'function'
|
||||||
|
? window.soonDefaultNewSoonName()
|
||||||
|
: normalizeSoonName('design.soon');
|
||||||
|
}
|
||||||
|
if (raw.indexOf('soondesign_file:') === 0 && typeof window.soonDisplayFileName === 'function') {
|
||||||
|
return window.soonDisplayFileName(raw);
|
||||||
|
}
|
||||||
|
return normalizeSoonName(raw);
|
||||||
|
}
|
||||||
|
|
||||||
var bridge = {
|
var bridge = {
|
||||||
readHistory: function () {
|
readHistory: function () {
|
||||||
try {
|
try {
|
||||||
@@ -234,11 +282,61 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
var key = typeof pathOrHandle === 'string' ? pathOrHandle : '';
|
var key = typeof pathOrHandle === 'string' ? pathOrHandle : '';
|
||||||
|
var cacheKey = key;
|
||||||
|
|
||||||
|
function cacheHitThen(hit) {
|
||||||
|
if (!hit || !hit.json) return null;
|
||||||
|
if (typeof window.soonLocalIsTemplateStale === 'function' &&
|
||||||
|
window.soonLocalIsTemplateStale(cacheKey, hit.meta)) {
|
||||||
|
if (typeof window.soonLocalRemove === 'function') {
|
||||||
|
return window.soonLocalRemove(cacheKey).then(function () { return null; });
|
||||||
|
}
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
if (cacheKey.indexOf('soondesign_file:') === 0 && hit.meta && hit.meta.name &&
|
||||||
|
typeof window.soonBindFileMeta === 'function') {
|
||||||
|
window.soonBindFileMeta(cacheKey, { name: hit.meta.name });
|
||||||
|
}
|
||||||
|
return Promise.resolve(hit.json);
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterNetworkJson(json, meta) {
|
||||||
|
if (!json) return null;
|
||||||
|
if (typeof window.soonLocalCacheAndThumb === 'function') {
|
||||||
|
return window.soonLocalCacheAndThumb(cacheKey, json, meta || {}).then(function () { return json; });
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key && typeof window.soonLocalGet === 'function') {
|
||||||
|
return window.soonLocalGet(cacheKey).then(function (hit) {
|
||||||
|
return cacheHitThen(hit).then(function (cached) {
|
||||||
|
if (cached) return cached;
|
||||||
|
return fetchFromNetwork(key, cacheKey);
|
||||||
|
});
|
||||||
|
}).catch(function () { return fetchFromNetwork(key, cacheKey); });
|
||||||
|
}
|
||||||
|
return fetchFromNetwork(key, cacheKey);
|
||||||
|
|
||||||
|
function fetchFromNetwork(key, cacheKey) {
|
||||||
if (key.indexOf('soondesign_session:') === 0) {
|
if (key.indexOf('soondesign_session:') === 0) {
|
||||||
|
if (typeof window.soonLocalGet === 'function') {
|
||||||
|
return window.soonLocalGet(cacheKey).then(function (hit) {
|
||||||
|
if (hit && hit.json) return hit.json;
|
||||||
try {
|
try {
|
||||||
var j = sessionStorage.getItem(key);
|
var j = sessionStorage.getItem(key);
|
||||||
if (!j && typeof localStorage !== 'undefined') j = localStorage.getItem(key);
|
if (!j && typeof localStorage !== 'undefined') j = localStorage.getItem(key);
|
||||||
return Promise.resolve(j ? JSON.parse(j) : null);
|
if (!j || j === 'idb') return null;
|
||||||
|
var parsed = JSON.parse(j);
|
||||||
|
return afterNetworkJson(parsed, { source: 'session', name: key });
|
||||||
|
} catch (e) { return null; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var j = sessionStorage.getItem(key);
|
||||||
|
if (!j && typeof localStorage !== 'undefined') j = localStorage.getItem(key);
|
||||||
|
if (!j || j === 'idb') return Promise.resolve(null);
|
||||||
|
return Promise.resolve(JSON.parse(j));
|
||||||
} catch (e) { return Promise.resolve(null); }
|
} catch (e) { return Promise.resolve(null); }
|
||||||
}
|
}
|
||||||
if (key && key.indexOf('soondesign_file:') === 0 && typeof fetch !== 'undefined') {
|
if (key && key.indexOf('soondesign_file:') === 0 && typeof fetch !== 'undefined') {
|
||||||
@@ -252,10 +350,22 @@
|
|||||||
.then(function (payload) {
|
.then(function (payload) {
|
||||||
if (!payload || !payload.ok || !payload.data) return null;
|
if (!payload || !payload.ok || !payload.data) return null;
|
||||||
var raw = payload.data.json;
|
var raw = payload.data.json;
|
||||||
|
var parsed;
|
||||||
if (typeof raw === 'string') {
|
if (typeof raw === 'string') {
|
||||||
try { return JSON.parse(raw); } catch (e) { return null; }
|
try { parsed = JSON.parse(raw); } catch (e) { return null; }
|
||||||
|
} else {
|
||||||
|
parsed = raw && typeof raw === 'object' ? raw : null;
|
||||||
}
|
}
|
||||||
return raw && typeof raw === 'object' ? raw : null;
|
if (!parsed) return null;
|
||||||
|
if (typeof window.soonApplyCloudMeta === 'function') {
|
||||||
|
window.soonApplyCloudMeta(payload.data);
|
||||||
|
}
|
||||||
|
return afterNetworkJson(parsed, {
|
||||||
|
source: 'cloud',
|
||||||
|
name: payload.data.name || '',
|
||||||
|
type: parsed.soonType || 1,
|
||||||
|
updatedAt: payload.data.updated_at || ''
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(function () { return null; });
|
.catch(function () { return null; });
|
||||||
}
|
}
|
||||||
@@ -265,30 +375,28 @@
|
|||||||
if (tplMatch) {
|
if (tplMatch) {
|
||||||
var apiBase = (window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base) || '';
|
var apiBase = (window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base) || '';
|
||||||
if (!apiBase) return Promise.resolve(null);
|
if (!apiBase) return Promise.resolve(null);
|
||||||
return fetch(apiBase + '/templates/' + tplMatch[1], { headers: { Accept: 'application/json' } })
|
return fetch(apiBase + '/templates/' + tplMatch[1] + '/file', { headers: { Accept: 'application/json' } })
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
if (!response.ok) return null;
|
if (!response.ok) return null;
|
||||||
return response.json();
|
return response.text();
|
||||||
})
|
})
|
||||||
.then(function (payload) {
|
.then(function (text) {
|
||||||
if (!payload || !payload.ok || !payload.data) return null;
|
if (!text) return null;
|
||||||
if (payload.data.id) {
|
var parsed;
|
||||||
window._soonTemplateMeta = {
|
try { parsed = JSON.parse(text); } catch (e) { return null; }
|
||||||
id: payload.data.id,
|
var tplMeta = window._soonTemplateMeta || {};
|
||||||
name: payload.data.name || '',
|
return afterNetworkJson(parsed, {
|
||||||
type: payload.data.type
|
source: 'template',
|
||||||
};
|
name: tplMeta.name || '',
|
||||||
}
|
type: tplMeta.type || (parsed.soonType || 1),
|
||||||
var raw = payload.data.json;
|
updatedAt: tplMeta.updated_at || ''
|
||||||
if (typeof raw === 'string') {
|
});
|
||||||
try { return JSON.parse(raw); } catch (e) { return null; }
|
|
||||||
}
|
|
||||||
return raw && typeof raw === 'object' ? raw : null;
|
|
||||||
})
|
})
|
||||||
.catch(function () { return null; });
|
.catch(function () { return null; });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
showOpenDialog: function (options) {
|
showOpenDialog: function (options) {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
@@ -327,16 +435,12 @@
|
|||||||
},
|
},
|
||||||
showSaveDialog: function (options) {
|
showSaveDialog: function (options) {
|
||||||
var raw = (options && options.defaultPath) ? options.defaultPath : '';
|
var raw = (options && options.defaultPath) ? options.defaultPath : '';
|
||||||
var defaultName = normalizeSoonName(raw || 'design.soon');
|
var isSoon = isSoonSaveDialog(options);
|
||||||
if (typeof window.soonDisplayFileName === 'function' &&
|
var defaultName = isSoon ? defaultSoonSaveDialogName(raw) : genericSaveDialogName(raw);
|
||||||
(raw.indexOf('soondesign_file:') === 0 || raw.indexOf('soondesign_template:') === 0)) {
|
var title = (options && options.title) ? options.title : (isSoon ? '保存到云端' : '保存文件');
|
||||||
defaultName = window.soonDisplayFileName(raw);
|
|
||||||
}
|
|
||||||
var title = (options && options.title) ? options.title : '保存到云端';
|
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
if (typeof layer === 'undefined' || !layer.open) {
|
if (typeof layer === 'undefined' || !layer.open) {
|
||||||
var fpFallback = defaultName;
|
resolve({ canceled: false, filePath: defaultName, useCloud: !!isSoon });
|
||||||
resolve({ canceled: false, filePath: fpFallback, useCloud: true });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var esc = function (s) {
|
var esc = function (s) {
|
||||||
@@ -359,10 +463,9 @@
|
|||||||
yes: function (index) {
|
yes: function (index) {
|
||||||
var input = document.getElementById('soonSaveDialogInput');
|
var input = document.getElementById('soonSaveDialogInput');
|
||||||
var name = input && input.value ? String(input.value).trim() : defaultName;
|
var name = input && input.value ? String(input.value).trim() : defaultName;
|
||||||
if (!name) name = defaultName;
|
name = resolveSaveDialogName(name, options, isSoon);
|
||||||
name = normalizeSoonName(name);
|
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
resolve({ canceled: false, filePath: name, useCloud: true });
|
resolve({ canceled: false, filePath: name, useCloud: !!isSoon });
|
||||||
},
|
},
|
||||||
btn2: function (index) {
|
btn2: function (index) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
@@ -397,21 +500,52 @@
|
|||||||
var cloudRef = parseCloudRef(name);
|
var cloudRef = parseCloudRef(name);
|
||||||
var fileName = normalizeSoonName(name);
|
var fileName = normalizeSoonName(name);
|
||||||
|
|
||||||
|
var parsedJson;
|
||||||
|
try { parsedJson = JSON.parse(str); } catch (e) { parsedJson = null; }
|
||||||
|
|
||||||
|
function cacheWriteForKey(targetKey, meta) {
|
||||||
|
if (!parsedJson || typeof window.soonLocalCacheAndThumb !== 'function') return Promise.resolve();
|
||||||
|
return window.soonLocalCacheAndThumb(targetKey, parsedJson, meta || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterCloudSave(res, prevKey) {
|
||||||
|
if (!res || !res.fileKey || !parsedJson) return res;
|
||||||
|
var meta = { source: 'cloud', name: res.name || fileName, type: parsedJson.soonType || 1 };
|
||||||
|
if (prevKey && prevKey !== res.fileKey && typeof window.soonLocalRenameKey === 'function') {
|
||||||
|
return window.soonLocalRenameKey(prevKey, res.fileKey, meta).then(function () { return res; });
|
||||||
|
}
|
||||||
|
return cacheWriteForKey(res.fileKey, meta).then(function () { return res; });
|
||||||
|
}
|
||||||
|
|
||||||
if (!getAccessToken()) {
|
if (!getAccessToken()) {
|
||||||
if (cloudRef && cloudRef.id) {
|
if (cloudRef && cloudRef.id) {
|
||||||
requireCloudAuth('保存');
|
requireCloudAuth('保存');
|
||||||
return Promise.reject(new Error('unauthorized'));
|
return Promise.reject(new Error('unauthorized'));
|
||||||
}
|
}
|
||||||
var sessionKey = name.indexOf('soondesign_session:') === 0
|
var sessionKey = typeof window.soonMakeSessionKey === 'function'
|
||||||
|
? window.soonMakeSessionKey(name)
|
||||||
|
: (name.indexOf('soondesign_session:') === 0
|
||||||
? name
|
? name
|
||||||
: 'soondesign_session:' + fileName.replace(/\.soon$/i, '') + '-' + Date.now();
|
: 'soondesign_session:' + fileName.replace(/\.soon$/i, ''));
|
||||||
try {
|
var prevSessionKey = name.indexOf('soondesign_session:') === 0 ? name : '';
|
||||||
sessionStorage.setItem(sessionKey, str);
|
function finishSessionSave() {
|
||||||
try { localStorage.setItem(sessionKey, str); } catch (e2) { /* ignore quota */ }
|
try { sessionStorage.setItem(sessionKey, 'idb'); } catch (e) { /* ignore */ }
|
||||||
return Promise.resolve({ fileKey: sessionKey, name: fileName, version: 0 });
|
return { fileKey: sessionKey, name: fileName, version: 0 };
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(new Error('save_failed'));
|
|
||||||
}
|
}
|
||||||
|
if (prevSessionKey && prevSessionKey !== sessionKey &&
|
||||||
|
typeof window.soonLocalRenameKey === 'function') {
|
||||||
|
return cacheWriteForKey(sessionKey, { source: 'session', name: fileName }).then(function () {
|
||||||
|
return window.soonLocalRenameKey(prevSessionKey, sessionKey, { source: 'session', name: fileName })
|
||||||
|
.then(finishSessionSave);
|
||||||
|
}).catch(function () {
|
||||||
|
return Promise.reject(new Error('save_failed'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return cacheWriteForKey(sessionKey, { source: 'session', name: fileName }).then(function () {
|
||||||
|
return finishSessionSave();
|
||||||
|
}).catch(function () {
|
||||||
|
return Promise.reject(new Error('save_failed'));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!requireCloudAuth('保存')) {
|
if (!requireCloudAuth('保存')) {
|
||||||
@@ -423,10 +557,20 @@
|
|||||||
if (ver == null && window._soonFileMeta && window._soonFileMeta.id === cloudRef.id) {
|
if (ver == null && window._soonFileMeta && window._soonFileMeta.id === cloudRef.id) {
|
||||||
ver = window._soonFileMeta.version;
|
ver = window._soonFileMeta.version;
|
||||||
}
|
}
|
||||||
return updateCloudFile(cloudRef.id, fileName, str, ver);
|
if (typeof window.soonResolveCloudFileName === 'function') {
|
||||||
|
var cloudSaveName = window.soonResolveCloudFileName(name);
|
||||||
|
if (cloudSaveName) fileName = cloudSaveName;
|
||||||
|
}
|
||||||
|
var prevCloudKey = name;
|
||||||
|
return updateCloudFile(cloudRef.id, fileName, str, ver).then(function (res) {
|
||||||
|
return afterCloudSave(res, prevCloudKey);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return createCloudFile(fileName, str);
|
var prevKey = name.indexOf('soondesign_session:') === 0 ? name : '';
|
||||||
|
return createCloudFile(fileName, str).then(function (res) {
|
||||||
|
return afterCloudSave(res, prevKey);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
readFile: function (pathOrHandle) {
|
readFile: function (pathOrHandle) {
|
||||||
if (pathOrHandle && pathOrHandle.getFile) {
|
if (pathOrHandle && pathOrHandle.getFile) {
|
||||||
|
|||||||
@@ -879,6 +879,8 @@
|
|||||||
<script src="../js/common/auth-session.js"></script>
|
<script src="../js/common/auth-session.js"></script>
|
||||||
<script src="../js/common/member-pay-core.js"></script>
|
<script src="../js/common/member-pay-core.js"></script>
|
||||||
<script src="../js/common/cloud-files.js"></script>
|
<script src="../js/common/cloud-files.js"></script>
|
||||||
|
<script src="../js/common/soon-local-store.js"></script>
|
||||||
|
<script src="../js/common/soon-recent.js"></script>
|
||||||
<script src="../js/common/asset-base.js"></script>
|
<script src="../js/common/asset-base.js"></script>
|
||||||
<script src="../js/common/portal-topbar.js"></script>
|
<script src="../js/common/portal-topbar.js"></script>
|
||||||
<script src="../js/common/portal-auth.js"></script>
|
<script src="../js/common/portal-auth.js"></script>
|
||||||
|
|||||||
@@ -559,15 +559,17 @@
|
|||||||
|
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
|
|
||||||
<div class="ui-button ui-button-active img-warp" id="front_side">
|
<div class="soon-side-tabs">
|
||||||
|
<div class="ui-button ui-button-active" id="front_side">
|
||||||
<span language="m" zh="正面" ozh="正面" en="Front"></span>
|
<span language="m" zh="正面" ozh="正面" en="Front"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui-button img-warp" id="back_side">
|
<div class="ui-button" id="back_side">
|
||||||
<span language="m" zh="背面" ozh="背面" en="Back"></span>
|
<span language="m" zh="背面" ozh="背面" en="Back"></span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
|
|
||||||
<div class="ui-button img-warp" id="version_change">
|
<div class="ui-button soon-tool-text-btn" id="version_change">
|
||||||
<span language="m" zh="切换" ozh="切换" en="Swap"></span>
|
<span language="m" zh="切换" ozh="切换" en="Swap"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -669,6 +671,8 @@
|
|||||||
<script src="../js/common/auth-session.js"></script>
|
<script src="../js/common/auth-session.js"></script>
|
||||||
<script src="../js/common/member-pay-core.js"></script>
|
<script src="../js/common/member-pay-core.js"></script>
|
||||||
<script src="../js/common/cloud-files.js"></script>
|
<script src="../js/common/cloud-files.js"></script>
|
||||||
|
<script src="../js/common/soon-local-store.js"></script>
|
||||||
|
<script src="../js/common/soon-recent.js"></script>
|
||||||
<script src="../js/common/asset-base.js"></script>
|
<script src="../js/common/asset-base.js"></script>
|
||||||
<script src="../js/common/portal-topbar.js"></script>
|
<script src="../js/common/portal-topbar.js"></script>
|
||||||
<script src="../js/common/portal-auth.js"></script>
|
<script src="../js/common/portal-auth.js"></script>
|
||||||
|
|||||||
@@ -140,6 +140,8 @@
|
|||||||
|
|
||||||
<script src="../js/common/auth-session.js"></script>
|
<script src="../js/common/auth-session.js"></script>
|
||||||
<script src="../js/common/cloud-files.js"></script>
|
<script src="../js/common/cloud-files.js"></script>
|
||||||
|
<script src="../js/common/soon-local-store.js"></script>
|
||||||
|
<script src="../js/common/soon-recent.js"></script>
|
||||||
<script src="../vendor/layui/layui.js"></script>
|
<script src="../vendor/layui/layui.js"></script>
|
||||||
<script src="../vendor/js/qrcode.min.js"></script>
|
<script src="../vendor/js/qrcode.min.js"></script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user