feat: 完善模型用量趋势与全局手账卡片并补齐阶段验收

This commit is contained in:
2026-09-05 20:40:36 +08:00
parent 02dd585a4e
commit 8d626ee16b
38 changed files with 733 additions and 86 deletions
@@ -4,16 +4,20 @@ import { t } from '@/i18n'
import { getCommunityThemePreviewCss, mockCommunityThemes } from '@/services/themePackageService'
import tokensCss from '@/styles/tokens.css?raw'
const props = defineProps<{ themeId: string }>()
const props = defineProps<{ themeId: string; name?: string; css?: string }>()
const emit = defineEmits<{ (event: 'close'): void }>()
const theme = computed(() => mockCommunityThemes.find(item => item.theme_id === props.themeId))
const theme = computed(() => props.name ? { name: props.name } : mockCommunityThemes.find(item => item.theme_id === props.themeId))
const previewDocument = computed(() => {
// Only bundled community CSS enters this script-free, isolated document.
// Both imported and bundled CSS are previewed in a script-free isolated document.
// Previewing never installs a theme or changes application styles/storage.
const doc = document.implementation.createHTMLDocument(theme.value?.name ?? '')
doc.documentElement.dataset.theme = props.themeId
const policy = doc.createElement('meta')
policy.httpEquiv = 'Content-Security-Policy'
policy.content = "default-src 'none'; style-src 'unsafe-inline'; img-src data:; font-src data:; base-uri 'none'; form-action 'none'"
doc.head.append(policy)
const style = doc.createElement('style')
style.textContent = `${tokensCss}\n${getCommunityThemePreviewCss(props.themeId)}\nbody { margin:0; padding:24px; background:var(--color-background-primary); color:var(--color-text-primary); font:16px/1.6 system-ui; } article { padding:20px; border:1px solid var(--color-border-default); border-radius:8px; background:var(--color-surface-primary); } p { color:var(--color-text-secondary); } button { padding:8px 16px; border:0; border-radius:6px; background:var(--color-accent-primary); color:white; }`
style.textContent = `${tokensCss}\n${props.css ?? getCommunityThemePreviewCss(props.themeId)}\nbody { margin:0; padding:24px; background:var(--color-background-primary); color:var(--color-text-primary); font:16px/1.6 system-ui; } article { padding:20px; border:1px solid var(--color-border-default); border-radius:8px; background:var(--color-surface-primary); } p { color:var(--color-text-secondary); } button { padding:8px 16px; border:0; border-radius:6px; background:var(--color-accent-primary); color:white; }`
doc.head.append(style)
const article = doc.createElement('article')
article.className = 'panel'
@@ -21,6 +21,12 @@ it('downloads a URL for inspection without automatically installing it', async (
await vi.waitFor(() => expect(useThemeStore().pendingInspection?.compatible).toBe(true))
expect(useThemeStore().isThemeInstalled('paper-moments')).toBe(false)
expect(wrapper.get('.inspection-result').text()).toContain('纸间时光')
await wrapper.findAll('button').find(button => button.text() === '预览主题效果')!.trigger('click')
const preview = wrapper.get('iframe')
expect(preview.attributes('sandbox')).toBe('')
expect(preview.attributes('srcdoc')).toContain('Content-Security-Policy')
expect(preview.attributes('srcdoc')).toContain('data-theme="paper-moments"')
expect(useThemeStore().isThemeInstalled('paper-moments')).toBe(false)
})
it('ignores a URL response after the dialog is cancelled', async () => {
@@ -9,6 +9,7 @@ import CommunityThemePreview from './CommunityThemePreview.vue'
import paperMomentsUrl from '@/assets/themes/paper-moments.theme?url'
const themeStore = useThemeStore()
const previewImport = ref(false)
const activeTab = ref<'installed' | 'community'>('installed')
const showImportDialog = ref(false)
@@ -22,6 +23,7 @@ let importGeneration = 0
let downloadController: AbortController | undefined
function resetImport() {
previewImport.value = false
importGeneration++
downloadController?.abort()
importing.value = false
@@ -253,6 +255,7 @@ onMounted(() => {
<summary>将要安装的 CSS{{ themeStore.pendingInspection.css.length }} 字符</summary>
<pre>{{ themeStore.pendingInspection.css }}</pre>
</details>
<button type="button" class="button-secondary" @click="previewImport = true">预览主题效果</button>
</div>
<div v-else class="upload-area">
@@ -280,6 +283,7 @@ onMounted(() => {
</div>
</div>
</section>
<CommunityThemePreview v-if="previewImport && themeStore.pendingInspection?.compatible" :theme-id="themeStore.pendingInspection.manifest.theme_id" :name="themeStore.pendingInspection.manifest.name" :css="themeStore.pendingInspection.css" @close="previewImport = false" />
</template>
<style scoped>