Fix frontend blank page issue

- Simplify Home.vue to avoid API loading errors
- Use built-in template data instead of API calls
- Add simple error handling for API requests
- Add test page for debugging
This commit is contained in:
Poker Design Developer
2026-05-31 19:35:53 +08:00
parent 5dbcebf7a2
commit 48b6fb0a39
5 changed files with 212 additions and 147 deletions

View File

@@ -4,18 +4,6 @@ const API_BASE = '/api'
export async function getProjects() { export async function getProjects() {
const response = await axios.get(`${API_BASE}/projects/`) const response = await axios.get(`${API_BASE}/projects/`)
// Handle Django REST Framework pagination format
if (response.data.value !== undefined) {
return response.data.value
}
return response.data
}
export async function getTemplates() {
const response = await axios.get(`${API_BASE}/templates/`)
if (response.data.value !== undefined) {
return response.data.value
}
return response.data return response.data
} }

View File

@@ -3,7 +3,7 @@ import { createPinia } from 'pinia'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import App from './App.vue' import App from './App.vue'
import router from './router/index.js' import router from './router'
const app = createApp(App) const app = createApp(App)
app.use(createPinia()) app.use(createPinia())

View File

@@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue' import Home from '@/views/Home.vue'
import Editor from '@/views/Editor.vue' import Editor from '@/views/Editor.vue'
import Test from '@/views/Test.vue'
const routes = [ const routes = [
{ {
@@ -8,6 +9,11 @@ const routes = [
name: 'Home', name: 'Home',
component: Home component: Home
}, },
{
path: '/test',
name: 'Test',
component: Test
},
{ {
path: '/editor/:projectId?', path: '/editor/:projectId?',
name: 'Editor', name: 'Editor',

View File

@@ -1,152 +1,136 @@
<template> <template>
<el-container class="home-container"> <div class="home-container">
<el-header> <header class="header">
<h1>扑克牌设计管理系统</h1> <h1>扑克牌设计管理系统</h1>
<button @click="createNewProject" class="create-btn"> <button @click="createNewProject" class="create-btn">创建新项目</button>
<el-icon><Plus /></el-icon> </header>
创建新项目
</button>
</el-header>
<el-main> <main class="main">
<h2>选择或创建项目</h2> <h2>选择或创建项目</h2>
<el-tabs v-model="activeTab" type="card"> <div class="tabs">
<el-tab-pane label="模板系列" name="templates"> <button
<div class="template-grid"> :class="{ active: activeTab === 'templates' }"
<div @click="activeTab = 'templates'"
v-for="template in templates"
:key="template.id"
class="template-card"
@click="selectTemplate(template.id)"
> >
<div class="template-preview"> 模板系列
<img :src="template.preview_image" alt="" v-if="template.preview_image"> </button>
<div class="placeholder-preview">{{ template.name.substring(0,2) }}</div> <button
:class="{ active: activeTab === 'existing' }"
@click="activeTab = 'existing'"
>
已有项目
</button>
</div> </div>
<div v-if="activeTab === 'templates'" class="template-grid">
<div v-for="template in templates" :key="template.id" class="template-card" @click="selectTemplate(template.id)">
<div class="template-preview">{{ template.name.substring(0, 2) }}</div>
<h3>{{ template.name }}</h3> <h3>{{ template.name }}</h3>
<p>{{ template.description }}</p> <p>{{ template.description }}</p>
</div> </div>
</div> </div>
</el-tab-pane>
<el-tab-pane label="已有项目" name="existing"> <div v-if="activeTab === 'existing'" class="project-list">
<div class="project-list"> <div v-if="loading">加载中...</div>
<div v-else-if="projects.length === 0" class="empty-state">
暂无项目请创建新项目
</div>
<div v-else>
<div v-for="project in projects" :key="project.id" class="project-item"> <div v-for="project in projects" :key="project.id" class="project-item">
<div class="project-info"> <div class="project-info">
<h3>{{ project.name }}</h3> <h3>{{ project.name }}</h3>
<p>创建于: {{ formatDate(project.created_at) }}</p> <p>创建于: {{ formatDate(project.created_at) }}</p>
</div> </div>
<div class="project-actions"> <div class="project-actions">
<el-button @click="editProject(project.id)">编辑</el-button> <button @click="editProject(project.id)">编辑</button>
<el-button type="danger" @click="deleteProject(project.id)">删除</el-button> <button @click="deleteProject(project.id)" style="color: red;">删除</button>
</div> </div>
</div> </div>
<div v-if="projects.length === 0" class="empty-state">
暂无项目请创建新项目
</div> </div>
</div> </div>
</el-tab-pane> </main>
</el-tabs> </div>
</el-main>
</el-container>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { Plus } from '@element-plus/icons-vue' import axios from 'axios'
import { getProjects, createProject as apiCreateProject, deleteProject as apiDeleteProject, getTemplates } from '@/api/project'
import { getTemplate as apiGetTemplate } from '@/api/template'
import { ElMessage, ElMessageBox } from 'element-plus'
const router = useRouter() const router = useRouter()
const activeTab = ref('templates') const activeTab = ref('templates')
const projects = ref([]) const projects = ref([])
const templates = ref([]) const templates = ref([])
const loading = ref(false)
// 内置模板数据不依赖API
const defaultTemplates = [
{ id: 'classic', name: '经典风格', description: '标准扑克牌设计,传统花色和字体' },
{ id: 'modern', name: '现代简约', description: '扁平化设计,简洁线条' },
{ id: 'cartoon', name: '卡通风格', description: 'Q版可爱人像圆润花色图案' },
{ id: 'vintage', name: '复古风格', description: '复古色调和纹理,装饰性边框' }
]
onMounted(async () => { onMounted(async () => {
templates.value = defaultTemplates
await loadProjects() await loadProjects()
await loadTemplates()
}) })
async function loadProjects() { async function loadProjects() {
loading.value = true
try { try {
projects.value = await getProjects() const response = await axios.get('/api/projects/')
projects.value = response.data.value || response.data || []
} catch (error) { } catch (error) {
console.error('Failed to load projects:', error) console.error('Failed to load projects:', error)
} projects.value = []
} } finally {
loading.value = false
async function loadTemplates() {
try {
templates.value = await getTemplates()
} catch (error) {
console.error('Failed to load templates:', error)
} }
} }
async function createNewProject() { async function createNewProject() {
if (activeTab.value === 'existing' && projects.value.length > 0) {
ElMessage.warning('请先选择模板')
return
}
try { try {
const templateId = activeTab.value === 'templates' const response = await axios.post('/api/projects/', {
? templates.value[0]?.id || 'classic' name: `新项目 ${new Date().toLocaleDateString()}`,
: 'classic' template_id: 'classic'
const newProject = await apiCreateProject({
name: `未命名项目 ${new Date().toLocaleDateString()}`,
template_id: templateId
}) })
const projectId = response.data.id
router.push(`/editor/${newProject.id}`) router.push(`/editor/${projectId}`)
ElMessage.success('项目创建成功')
} catch (error) { } catch (error) {
ElMessage.error('项目创建失败') console.error('Failed to create project:', error)
alert('创建项目失败: ' + error.message)
} }
} }
async function selectTemplate(templateId) { async function selectTemplate(templateId) {
try { try {
const template = await apiGetTemplate(templateId) const response = await axios.post('/api/projects/', {
const newProject = await apiCreateProject({ name: `${templateId} - 新项目`,
name: `${template.name} - 新项目`,
template_id: templateId template_id: templateId
}) })
const projectId = response.data.id
router.push(`/editor/${newProject.id}`) router.push(`/editor/${projectId}`)
ElMessage.success('项目创建成功')
} catch (error) { } catch (error) {
ElMessage.error('项目创建失败') console.error('Failed to create project:', error)
alert('创建项目失败: ' + error.message)
} }
} }
async function editProject(projectId) { function editProject(projectId) {
router.push(`/editor/${projectId}`) router.push(`/editor/${projectId}`)
} }
async function deleteProject(projectId) { async function deleteProject(projectId) {
try { if (!confirm('确定要删除这个项目吗?')) return
await ElMessageBox.confirm(
'确定要删除这个项目吗?此操作无法撤销。',
'确认删除',
{
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
}
)
await apiDeleteProject(projectId) try {
await axios.delete(`/api/projects/${projectId}/`)
await loadProjects() await loadProjects()
ElMessage.success('项目已删除')
} catch (error) { } catch (error) {
if (error !== 'cancel') { console.error('Failed to delete project:', error)
ElMessage.error('删除失败') alert('删除失败: ' + error.message)
}
} }
} }
@@ -157,22 +141,22 @@ function formatDate(dateString) {
<style scoped> <style scoped>
.home-container { .home-container {
height: 100vh; min-height: 100vh;
background: #f5f5f5;
} }
.el-header { .header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; color: white;
padding: 0 30px; padding: 20px 30px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
} }
.el-header h1 { .header h1 {
font-size: 24px; font-size: 24px;
font-weight: 600; margin: 0;
} }
.create-btn { .create-btn {
@@ -183,23 +167,43 @@ function formatDate(dateString) {
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
display: flex; font-size: 14px;
align-items: center;
gap: 8px;
transition: background 0.3s;
} }
.create-btn:hover { .create-btn:hover {
background: #f0f0f0; background: #f0f0f0;
} }
.el-main { .main {
padding: 30px; padding: 30px;
max-width: 1200px;
margin: 0 auto;
} }
h2 { .main h2 {
margin-bottom: 20px; margin-bottom: 20px;
font-weight: 600; color: #333;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tabs button {
padding: 10px 20px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
border-radius: 4px;
font-size: 14px;
}
.tabs button.active {
background: #667eea;
color: white;
border-color: #667eea;
} }
.template-grid { .template-grid {
@@ -209,6 +213,7 @@ h2 {
} }
.template-card { .template-card {
background: white;
border: 2px solid #e0e0e0; border: 2px solid #e0e0e0;
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
@@ -223,43 +228,34 @@ h2 {
.template-preview { .template-preview {
width: 100%; width: 100%;
height: 150px; height: 120px;
background: #f5f5f5; background: #f0f0f0;
border-radius: 4px; border-radius: 4px;
margin-bottom: 15px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden; font-size: 36px;
color: #667eea;
margin-bottom: 15px;
} }
.template-preview img { .template-card h3 {
width: 100%; margin: 0 0 8px 0;
height: 100%;
object-fit: cover;
}
.placeholder-preview {
font-size: 48px;
color: #999;
}
template-card h3 {
font-size: 16px;
margin-bottom: 8px;
color: #333; color: #333;
font-size: 16px;
} }
template-card p { .template-card p {
font-size: 14px; margin: 0;
color: #666; color: #666;
font-size: 14px;
line-height: 1.5; line-height: 1.5;
} }
.project-list { .project-list {
display: flex; background: white;
flex-direction: column; border-radius: 8px;
gap: 15px; padding: 20px;
} }
.project-item { .project-item {
@@ -269,6 +265,7 @@ template-card p {
padding: 15px; padding: 15px;
background: #f5f5f5; background: #f5f5f5;
border-radius: 6px; border-radius: 6px;
margin-bottom: 10px;
} }
.project-info h3 { .project-info h3 {
@@ -278,8 +275,8 @@ template-card p {
.project-info p { .project-info p {
margin: 0; margin: 0;
font-size: 14px;
color: #666; color: #666;
font-size: 14px;
} }
.project-actions { .project-actions {
@@ -287,10 +284,17 @@ template-card p {
gap: 10px; gap: 10px;
} }
.project-actions button {
padding: 6px 12px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
border-radius: 4px;
}
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 50px; padding: 50px;
color: #999; color: #999;
font-size: 16px;
} }
</style> </style>

View File

@@ -0,0 +1,67 @@
<template>
<div style="padding: 20px; font-family: Arial, sans-serif;">
<h1>扑克牌设计管理系统 - 测试页面</h1>
<p>如果你能看到这个页面说明Vue正常工作</p>
<div style="margin-top: 20px;">
<h2>API测试</h2>
<button @click="testAPI" style="padding: 10px 20px; cursor: pointer;">
测试API连接
</button>
<p v-if="apiResult">API返回: {{ apiResult }}</p>
<p v-if="apiError" style="color: red;">错误: {{ apiError }}</p>
</div>
<div style="margin-top: 20px;">
<h2>项目列表</h2>
<div v-if="loading">加载中...</div>
<div v-else-if="projects.length > 0">
<div v-for="project in projects" :key="project.id" style="border: 1px solid #ccc; padding: 10px; margin: 10px 0;">
<h3>{{ project.name }}</h3>
<p>ID: {{ project.id }}</p>
<p>创建时间: {{ project.created_at }}</p>
</div>
</div>
<div v-else>没有项目</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const projects = ref([])
const loading = ref(false)
const apiResult = ref('')
const apiError = ref('')
onMounted(async () => {
await loadProjects()
})
async function loadProjects() {
loading.value = true
try {
const response = await axios.get('/api/projects/')
projects.value = response.data.value || response.data
apiResult.value = JSON.stringify(response.data, null, 2)
} catch (error) {
apiError.value = error.message
console.error('Failed to load projects:', error)
} finally {
loading.value = false
}
}
async function testAPI() {
try {
const response = await axios.get('/')
apiResult.value = JSON.stringify(response.data, null, 2)
apiError.value = ''
} catch (error) {
apiError.value = error.message
apiResult.value = ''
}
}
</script>