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() {
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
}

View File

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

View File

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

View File

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