diff --git a/package.json b/package.json index 8949d3c..b6e11aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tallynote", - "version": "1.1.38", + "version": "1.1.39", "private": true, "type": "module", "packageManager": "pnpm@9.0.6", diff --git a/shared/contracts.ts b/shared/contracts.ts index 19143e0..da4a56a 100644 --- a/shared/contracts.ts +++ b/shared/contracts.ts @@ -107,7 +107,7 @@ export const updateApplySchema = z.object({ export const updateDownloadSchema = z.object({ version: z.string().trim().regex(/^v?(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/), - confirm: z.literal(true), + confirm: z.boolean().default(true).optional(), }).strict(); export type ApiError = { diff --git a/web-next/src/main.tsx b/web-next/src/main.tsx index b5e5b9c..8845c93 100644 --- a/web-next/src/main.tsx +++ b/web-next/src/main.tsx @@ -34,7 +34,7 @@ function App() { const notify = useCallback((message: string, kind: "success" | "error" | "info" = "info") => { // The placement container owns the responsive right inset. Keeping the // item offset at zero avoids pushing narrow-screen notices off canvas. - const options = { content: message, duration: 4200, placement: "top-right" as const, offset: [0, 76] as [number, number], zIndex: 5000 }; + const options = { content: message, duration: 4200, placement: "top-right" as const, offset: [24, 76] as [number, number], zIndex: 6000 }; const show = kind === "success" ? NotificationPlugin.success : kind === "error" ? NotificationPlugin.error : NotificationPlugin.info; void show(options); }, []); diff --git a/web-next/src/pages/update/UpdatePage.tsx b/web-next/src/pages/update/UpdatePage.tsx index 5f44d81..aafd90a 100644 --- a/web-next/src/pages/update/UpdatePage.tsx +++ b/web-next/src/pages/update/UpdatePage.tsx @@ -245,6 +245,7 @@ export default function UpdatePage({ const [loading, setLoading] = useState(true); const [checking, setChecking] = useState(false); const [error, setError] = useState(""); + const [modalError, setModalError] = useState(""); const [pollError, setPollError] = useState(""); const [actionBusy, setActionBusy] = useState(false); const [reloadReady, setReloadReady] = useState(false); @@ -457,16 +458,20 @@ export default function UpdatePage({ if (!latest) return; setActionBusy(true); setError(""); + setModalError(""); try { const response = await api<{ job: UpdateJob }>("/api/update/download", { method: "POST", - body: JSON.stringify({ version: latest.version }), + body: JSON.stringify({ version: latest.version, confirm: true }), }); setLiveInfo((current) => (current ? { ...current, job: response.job } : current)); setConfirmReadyToDownload(false); notify?.("已提交下载更新包请求,后台下载中", "info"); } catch (e) { - setError((e as Error).message); + const msg = (e as Error).message; + setError(msg); + setModalError(msg); + notify?.(msg, "error"); } finally { setActionBusy(false); } @@ -477,15 +482,19 @@ export default function UpdatePage({ if (!latest) return; setActionBusy(true); setError(""); + setModalError(""); try { const response = await api<{ job: UpdateJob }>("/api/update/apply", { method: "POST", - body: JSON.stringify({ version: latest.version, confirm: true }), + body: JSON.stringify({ version: latest.version, confirm: true, ...(job?.id ? { jobId: job.id } : {}) }), }); setLiveInfo((current) => (current ? { ...current, job: response.job } : current)); notify?.("已提交升级请求,正在备份并平滑重启…", "info"); } catch (e) { - setError((e as Error).message); + const msg = (e as Error).message; + setError(msg); + setModalError(msg); + notify?.(msg, "error"); } finally { setActionBusy(false); } @@ -751,7 +760,7 @@ export default function UpdatePage({ size="large" onClick={() => { setConfirmReadyToDownload(true); - setShowUpgradeModal(true); + setModalError(""); setShowUpgradeModal(true); }} disabled={actionBusy} icon={} @@ -834,6 +843,13 @@ export default function UpdatePage({ onClose={() => setShowUpgradeModal(false)} >
+ {modalError && ( +
+ + {modalError} +
+ )} + {/* 4 Steps Horizontal (Exact ASCII: (1) 版本确认 (2) 下载更新包 (3) 校验与准备 (4) 重启恢复) */}
diff --git a/web-next/src/styles/theme.css b/web-next/src/styles/theme.css index a6ee31e..9ba9b38 100644 --- a/web-next/src/styles/theme.css +++ b/web-next/src/styles/theme.css @@ -116,7 +116,35 @@ input:focus-visible, textarea:focus-visible, select:focus-visible { transition: all 0.22s cubic-bezier(0.16, 1, 0.3, 1) !important; } .t-notification__show--top-right { - right: 20px !important; + top: 76px !important; + right: 24px !important; + z-index: 6000 !important; +} +@media (max-width: 768px) { + .t-notification__show--top-right { + top: 16px !important; + right: 16px !important; + left: 16px !important; + width: auto !important; + } +} +.tn-modal-error-banner { + display: flex; + align-items: center; + gap: 10px; + padding: 11px 16px; + margin-bottom: 16px; + border-radius: 6px; + background: #fef2f2; + border: 1px solid #fecaca; + color: #991b1b; + font-size: 13.5px; + font-weight: 500; + line-height: 1.4; +} +.tn-modal-error-banner svg { + flex-shrink: 0; + color: #dc2626; } /* 核心对齐修复:清除 TDesign 默认的 margin-top: 8px 导致的文字下沉不居中 */