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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
67
frontend/src/views/Test.vue
Normal file
67
frontend/src/views/Test.vue
Normal 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>
|
||||
Reference in New Issue
Block a user