diff --git a/scripts/publish-viewer.sh b/scripts/publish-viewer.sh new file mode 100644 index 0000000..ff7a5de --- /dev/null +++ b/scripts/publish-viewer.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# ===================================================================== +# publish-viewer.sh — u-ppt 查看器一键发布 +# 流程: 构建 → 全量上传七牛 bucket → 可达性核对 → CDN 缓存刷新 +# 用法: bash scripts/publish-viewer.sh [--check-only] +# 配套技能: publish-uppt-viewer (踩坑记录见技能文档) +# ===================================================================== +set -e + +PROJECT="/e/wk-lab/u-ppt" +BUCKET="u-res" +CDN="https://img.1216.top" + +cd "$PROJECT" + +if [ "$1" != "--check-only" ]; then + echo "== [1/4] 类型检查 + 构建 ==" + npx vue-tsc -b || { echo "vue-tsc 失败,中止"; exit 1; } + npm run build || { echo "构建失败,中止"; exit 1; } + cd dist + echo "== [2/4] 全量上传 (viewer.html + assets/*) ==" + qshell rput "$BUCKET" viewer.html viewer.html --overwrite > /dev/null 2>&1 || echo "上传 viewer.html 失败" + for f in $(ls assets/); do + qshell rput "$BUCKET" "assets/$f" "assets/$f" --overwrite > /dev/null 2>&1 || echo "上传 assets/$f 失败" + done +else + cd dist + echo "== check-only: 跳过构建上传 ==" +fi + +echo "== [3/4] 可达性核对 ==" +FAIL=0 +for f in viewer.html $(ls assets/ | sed 's|^|assets/|'); do + code=$(curl -s -o /dev/null -w "%{http_code}" "$CDN/$f") + if [ "$code" != "200" ]; then echo "FAIL $code $f"; FAIL=1; fi +done +[ "$FAIL" = "0" ] && echo "ALL-200" + +echo "== [4/4] 刷新 CDN 缓存 ==" +echo "$CDN/viewer.html" > /tmp/uppt-refresh.txt +echo "$CDN/index.html" >> /tmp/uppt-refresh.txt +qshell cdnrefresh -i /tmp/uppt-refresh.txt 2>&1 | grep -o "Code: [0-9]*" + +echo "== 完成: $CDN/viewer.html ==" diff --git a/src/core/bg.ts b/src/core/bg.ts index 9b0df87..7805843 100644 --- a/src/core/bg.ts +++ b/src/core/bg.ts @@ -70,3 +70,24 @@ export function resolveBg(key: string): string { if (key === 'g-soft') return 'linear-gradient(135deg, ' + (t.panel || '#f8fafc') + ' 0%, ' + (t.bg || '#ffffff') + ' 100%)' return t[key] || '#ffffff' } + +/** + * 颜色解析:dark=true 时把深色文字键反相为浅色,确保深底可见。 + * 安全:非主题键、非合法 hex 一律回退,杜绝任意 CSS/HTML 经 color 注入 + */ +export function resolveColor(key: string | undefined, dark: boolean): string { + const t = (getAllThemes()[getTheme()] || {}) as unknown as Record + if (!key) return '' + if (key.charAt(0) === '#') { + if (!isValidHex(key)) return dark ? '#ffffff' : (t.text || '#1e293b') + if (dark && isDarkHex(key)) return '#ffffff' + return key + } + if (dark) { + if (key === 'text') return '#ffffff' + if (key === 'muted') return 'rgba(255,255,255,0.72)' + if (key === 'primary') return '#ffffff' + return t[key] || '#ffffff' + } + return t[key] || t.text || '#1e293b' +} diff --git a/src/core/share.ts b/src/core/share.ts index 9500a7f..02d3297 100644 --- a/src/core/share.ts +++ b/src/core/share.ts @@ -7,7 +7,7 @@ import { store } from './store' import { ossUpload, isTauri } from './bridge' import { canUploadNow, isOssEnabled, buildUploadReq } from './assets' -import type { OssCfg } from './types' +import type { OssCfg, Deck } from './types' /** 分享发布前置校验:不满足时返回中文原因 */ export function shareReady(): { ok: boolean; reason?: string } { @@ -97,7 +97,11 @@ export async function publishDeck(): Promise<{ url: string; jsonUrl: string }> { const shareId = genShareId() const key = shareKey(cfg, shareId) - const dataBase64 = btoa(unescape(encodeURIComponent(JSON.stringify(store.getDeck())))) + // 深拷贝后剥离演讲者备注(slide.note),批注(el.style.annotations)保留照常渲染; + // 置 undefined 使 JSON.stringify 不输出该字段 + const deck = JSON.parse(JSON.stringify(store.getDeck())) as Deck + for (const slide of deck.slides || []) slide.note = undefined + const dataBase64 = btoa(unescape(encodeURIComponent(JSON.stringify(deck)))) const url = await ossUpload(buildUploadReq(cfg, key, dataBase64, 'application/json')) if (!url) throw new Error('上传分享数据失败:ossUpload 未返回 URL') const jsonUrl = url.split('#')[0] diff --git a/src/core/store.ts b/src/core/store.ts index a0905ce..54d3928 100644 --- a/src/core/store.ts +++ b/src/core/store.ts @@ -236,33 +236,12 @@ function syncActiveLibItem() { } /* ---------- 颜色/背景解析:纯函数已抽至 ./bg,此处再导出保持调用方 import 路径不变 ---------- */ -export { isDarkBg, resolveBg, isDarkHex, isValidHex } from './bg' -import { isDarkHex, isValidHex, getAllThemes, registerBgThemeGetter, CUSTOM_THEME_KEY } from './bg' +export { isDarkBg, resolveBg, isDarkHex, isValidHex, resolveColor, getAllThemes } from './bg' +import { getAllThemes, registerBgThemeGetter, CUSTOM_THEME_KEY } from './bg' // 注入当前主题 getter,bg.ts 解析背景时读取(viewer 侧无需引入本 store) registerBgThemeGetter(() => state.deck.theme) -/** - * 颜色解析:dark=true 时把深色文字键反相为浅色,确保深底可见。 - * 安全:非主题键、非合法 hex 一律回退,杜绝任意 CSS/HTML 经 color 注入 - */ -export function resolveColor(key: string | undefined, dark: boolean): string { - const t = (getAllThemes()[state.deck.theme] || {}) as unknown as Record - if (!key) return '' - if (key.charAt(0) === '#') { - if (!isValidHex(key)) return dark ? '#ffffff' : (t.text || '#1e293b') - if (dark && isDarkHex(key)) return '#ffffff' - return key - } - if (dark) { - if (key === 'text') return '#ffffff' - if (key === 'muted') return 'rgba(255,255,255,0.72)' - if (key === 'primary') return '#ffffff' - return t[key] || '#ffffff' - } - return t[key] || t.text || '#1e293b' -} - /* ---------- 内部:执行 Op 并推入历史 ---------- */ /** 执行一个 Op,计算逆 Op,推入历史栈。返回是否执行成功 */ diff --git a/src/viewer/ViewerAnnotations.vue b/src/viewer/ViewerAnnotations.vue new file mode 100644 index 0000000..a529d93 --- /dev/null +++ b/src/viewer/ViewerAnnotations.vue @@ -0,0 +1,204 @@ + + + + + + diff --git a/src/viewer/ViewerApp.vue b/src/viewer/ViewerApp.vue index 6a9c3ff..10a65c6 100644 --- a/src/viewer/ViewerApp.vue +++ b/src/viewer/ViewerApp.vue @@ -10,6 +10,7 @@ import { parseShareHash, shareJsonUrlOf } from '../core/share' import { resolveBg, registerBgThemeGetter } from '../core/bg' import { CANVAS_W, CANVAS_H } from '../core/sample' import ElementView from '../components/editor/ElementView.vue' +import ViewerAnnotations from './ViewerAnnotations.vue' type Status = 'loading' | 'ready' | 'error' | 'invalid' @@ -151,6 +152,7 @@ onUnmounted(unbindEvents) :el="el" :bg="currentSlide.background" /> +
{{ (index + 1) + ' / ' + total }}