新增: 首页 + 资料导入 + PDF导出 + AI文档生成
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
<!-- =====================================================================
|
||||
HomePage.vue — 首页:品牌展示、快捷入口、最近文库
|
||||
===================================================================== -->
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { store } from '../core/store'
|
||||
import type { LibItem } from '../core/types'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'new-blank'): void
|
||||
(e: 'open-library'): void
|
||||
(e: 'import-materials'): void
|
||||
(e: 'open-settings'): void
|
||||
(e: 'toast', msg: string): void
|
||||
(e: 'open-deck'): void
|
||||
(e: 'ai-create'): void
|
||||
}>()
|
||||
|
||||
const libVersion = ref(0)
|
||||
const library = computed<LibItem[]>(() => {
|
||||
void libVersion.value
|
||||
return store.getLibrary().slice(0, 6) // 最近 6 个
|
||||
})
|
||||
|
||||
function bump() { libVersion.value++ }
|
||||
onMounted(bump)
|
||||
|
||||
function loadItem(id: string) {
|
||||
if (store.loadFromLibrary(id)) {
|
||||
emit('toast', '已加载:' + (library.value.find(x => x.id === id)?.name || ''))
|
||||
emit('open-deck')
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(ts: number): string {
|
||||
if (!ts) return ''
|
||||
const diff = Date.now() - ts
|
||||
if (diff < 60000) return '刚刚'
|
||||
if (diff < 3600000) return Math.floor(diff / 60000) + ' 分钟前'
|
||||
if (diff < 86400000) return Math.floor(diff / 3600000) + ' 小时前'
|
||||
const d = new Date(ts)
|
||||
return d.getMonth() + 1 + '/' + d.getDate()
|
||||
}
|
||||
|
||||
function firstSlideTitle(item: LibItem): string {
|
||||
const el = item?.deck?.slides?.[0]?.elements?.[0]
|
||||
return el?.type === 'title' ? (el.content || '无标题') : '无标题'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="home">
|
||||
<!-- 背景装饰 -->
|
||||
<div class="home-bg">
|
||||
<div class="home-glow top-right"></div>
|
||||
<div class="home-glow bottom-left"></div>
|
||||
</div>
|
||||
|
||||
<div class="home-inner">
|
||||
<!-- 品牌区 -->
|
||||
<header class="home-brand">
|
||||
<span class="home-logo">▦</span>
|
||||
<h1 class="home-title">u-ppt</h1>
|
||||
<p class="home-desc">轻量在线演示工具 · 支持 AI 创作与本地资料导入</p>
|
||||
</header>
|
||||
|
||||
<!-- 快捷入口 -->
|
||||
<div class="home-actions">
|
||||
<button class="action-card" @click="emit('new-blank')">
|
||||
<span class="action-icon">+</span>
|
||||
<span class="action-label">新建空白</span>
|
||||
<span class="action-hint">从空白页开始创作</span>
|
||||
</button>
|
||||
<button class="action-card" @click="emit('open-library')">
|
||||
<span class="action-icon">📁</span>
|
||||
<span class="action-label">从文库打开</span>
|
||||
<span class="action-hint">继续已有的演示</span>
|
||||
</button>
|
||||
<button class="action-card" @click="emit('import-materials')">
|
||||
<span class="action-icon">📂</span>
|
||||
<span class="action-label">导入资料</span>
|
||||
<span class="action-hint">图片/PDF/DOCX → AI 生成</span>
|
||||
</button>
|
||||
<button class="action-card accent" @click="emit('ai-create')">
|
||||
<span class="action-icon">🤖</span>
|
||||
<span class="action-label">AI 创作</span>
|
||||
<span class="action-hint">输入主题,AI 生成全套演示</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 最近文库 -->
|
||||
<div v-if="library.length > 0" class="home-recent">
|
||||
<h2 class="section-title">最近文档</h2>
|
||||
<div class="recent-list">
|
||||
<button
|
||||
v-for="item in library"
|
||||
:key="item.id"
|
||||
class="recent-item"
|
||||
@click="loadItem(item.id)"
|
||||
:title="item.name"
|
||||
>
|
||||
<span class="recent-icon">📄</span>
|
||||
<span class="recent-info">
|
||||
<span class="recent-name">{{ item.name }}</span>
|
||||
<span class="recent-meta">
|
||||
{{ item.deck.slides.length }} 页 · {{ formatTime(item.updatedAt || item.createdAt) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="recent-preview">{{ firstSlideTitle(item) }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部 -->
|
||||
<footer class="home-footer">
|
||||
<button class="btn ghost" @click="emit('open-settings')">⚙ 设置</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--ui-bg, #f1f5f9);
|
||||
}
|
||||
|
||||
/* 背景光晕 */
|
||||
.home-bg { position: absolute; inset: 0; pointer-events: none; }
|
||||
.home-glow {
|
||||
position: absolute;
|
||||
width: 480px; height: 480px;
|
||||
border-radius: 50%;
|
||||
filter: blur(120px);
|
||||
opacity: .08;
|
||||
}
|
||||
.home-glow.top-right { top: -120px; right: -80px; background: var(--ui-primary, #4f46e5); }
|
||||
.home-glow.bottom-left { bottom: -160px; left: -80px; background: var(--ui-primary, #4f46e5); }
|
||||
|
||||
.home-inner {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
padding: 40px 24px;
|
||||
max-width: 680px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 品牌 */
|
||||
.home-brand { text-align: center; }
|
||||
.home-logo {
|
||||
font-size: 56px; line-height: 1;
|
||||
color: var(--ui-primary, #4f46e5);
|
||||
display: block; margin-bottom: 8px;
|
||||
}
|
||||
.home-title {
|
||||
font-size: 32px; font-weight: 700;
|
||||
letter-spacing: -.02em;
|
||||
color: var(--ui-text, #1e293b);
|
||||
}
|
||||
.home-desc {
|
||||
margin-top: 6px;
|
||||
font-size: 15px;
|
||||
color: var(--ui-muted, #64748b);
|
||||
}
|
||||
|
||||
/* 快捷入口网格 */
|
||||
.home-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
.action-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--ui-border, #e2e8f0);
|
||||
border-radius: 12px;
|
||||
background: var(--ui-panel, #fff);
|
||||
cursor: pointer;
|
||||
transition: all .15s;
|
||||
text-align: left;
|
||||
}
|
||||
.action-card:hover {
|
||||
border-color: var(--ui-primary, #4f46e5);
|
||||
box-shadow: var(--shadow-md, 0 4px 12px rgba(15,23,42,.08));
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.action-card:active { transform: translateY(0); }
|
||||
.action-icon { font-size: 28px; line-height: 1; margin-bottom: 4px; }
|
||||
.action-label { font-size: 16px; font-weight: 600; color: var(--ui-text, #1e293b); }
|
||||
.action-hint { font-size: 13px; color: var(--ui-muted, #64748b); }
|
||||
|
||||
/* 最近文档 */
|
||||
.home-recent { width: 100%; }
|
||||
.section-title {
|
||||
font-size: 14px; font-weight: 600;
|
||||
color: var(--ui-muted, #64748b);
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
.recent-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--ui-border, #e2e8f0);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: var(--ui-panel, #fff);
|
||||
}
|
||||
.recent-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--ui-border, #f1f5f9);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
transition: background .1s;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
.recent-item:last-child { border-bottom: none; }
|
||||
.recent-item:hover { background: var(--ui-hover, #f1f5f9); }
|
||||
.recent-icon { font-size: 20px; flex-shrink: 0; }
|
||||
.recent-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
.recent-name {
|
||||
font-size: 14px; font-weight: 500;
|
||||
color: var(--ui-text, #1e293b);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.recent-meta {
|
||||
font-size: 12px;
|
||||
color: var(--ui-muted, #64748b);
|
||||
}
|
||||
.recent-preview {
|
||||
font-size: 13px;
|
||||
color: var(--ui-muted, #64748b);
|
||||
max-width: 160px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 底部 */
|
||||
.home-footer { display: flex; gap: 8px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user