后台布局

This commit is contained in:
李琦
2026-01-16 11:06:59 +08:00
parent de821a8645
commit c26e802309
10 changed files with 738 additions and 1504 deletions

View File

@@ -1,108 +1,132 @@
<template>
<div class="post-form-container">
<h1 class="page-title">{{ isEditing ? '编辑文章' : '新建文章' }}</h1>
<div class="form-container">
<form @submit.prevent="handleSubmit" class="post-form">
<!-- Title Field -->
<div class="form-group">
<label for="title">文章标题</label>
<input
type="text"
id="title"
v-model="form.title"
placeholder="请输入文章标题"
required
/>
<div class="error-message" v-if="errors.title">
{{ errors.title }}
</div>
</div>
<!-- Category Field -->
<div class="form-group">
<label for="category">分类</label>
<input
type="text"
id="category"
v-model="form.category"
placeholder="请输入文章分类"
required
/>
<div class="error-message" v-if="errors.category">
{{ errors.category }}
</div>
</div>
<!-- Date Field -->
<div class="form-group">
<label for="date">发布日期</label>
<input
type="date"
id="date"
v-model="form.date"
required
/>
<div class="error-message" v-if="errors.date">
{{ errors.date }}
</div>
</div>
<!-- Excerpt Field -->
<div class="form-group">
<label for="excerpt">文章摘要</label>
<textarea
id="excerpt"
v-model="form.excerpt"
placeholder="请输入文章摘要"
rows="3"
></textarea>
<div class="error-message" v-if="errors.excerpt">
{{ errors.excerpt }}
</div>
</div>
<!-- Content Field - 使用 Markdown 编辑器 -->
<div class="form-group">
<label>文章内容</label>
<div class="editor-wrapper">
<MdEditor
v-model="form.content"
theme="dark"
:preview="false"
placeholder="请输入 Markdown 内容..."
class="custom-md-editor"
/>
</div>
<div class="error-message" v-if="errors.content">
{{ errors.content }}
</div>
</div>
<!-- Is Published Field -->
<div class="form-group">
<label for="isPublished">发布状态</label>
<CustomSelect
v-model="form.isPublished"
:options="publishStatusOptions"
placeholder="请选择发布状态"
/>
<div class="error-message" v-if="errors.isPublished">
{{ errors.isPublished }}
</div>
</div>
<!-- Submit Buttons -->
<div class="form-actions">
<button type="button" class="cancel-btn" @click="handleCancel">
取消
</button>
<button type="submit" class="submit-btn" :disabled="isSubmitting">
{{ isSubmitting ? '提交中...' : (isEditing ? '更新文章' : '创建文章') }}
</button>
</div>
</form>
<div class="w-full animate-reveal">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-serif italic text-white">{{ isEditing ? '编辑文章' : '新建文章' }}</h1>
<div class="flex gap-3">
<button type="button" class="admin-btn-secondary" @click="handleCancel">
取消
</button>
<button type="button" class="admin-btn-primary" @click="handleSubmit" :disabled="isSubmitting">
{{ isSubmitting ? '提交中...' : (isEditing ? '更新文章' : '发布文章') }}
</button>
</div>
</div>
<form @submit.prevent="handleSubmit" class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Left Column: Main Content -->
<div class="lg:col-span-2 space-y-6">
<div class="admin-card p-6 space-y-6">
<!-- Title Field -->
<div class="space-y-2">
<label for="title" class="block text-sm font-medium text-art-muted uppercase tracking-wider">文章标题</label>
<input
type="text"
id="title"
v-model="form.title"
class="admin-input text-lg font-medium placeholder-white/20"
placeholder="请输入文章标题"
required
/>
<div class="text-art-error text-xs mt-1" v-if="errors.title">
{{ errors.title }}
</div>
</div>
<!-- Content Field -->
<div class="space-y-2">
<label class="block text-sm font-medium text-art-muted uppercase tracking-wider">文章内容</label>
<div class="border border-white/10 rounded-lg overflow-hidden focus-within:border-art-accent focus-within:ring-1 focus-within:ring-art-accent transition-all duration-200">
<MdEditor
v-model="form.content"
theme="dark"
:preview="false"
placeholder="请输入 Markdown 内容..."
class="custom-md-editor"
/>
</div>
<div class="text-art-error text-xs mt-1" v-if="errors.content">
{{ errors.content }}
</div>
</div>
<!-- Excerpt Field -->
<div class="space-y-2">
<label for="excerpt" class="block text-sm font-medium text-art-muted uppercase tracking-wider">文章摘要</label>
<textarea
id="excerpt"
v-model="form.excerpt"
rows="4"
class="admin-input resize-none"
placeholder="请输入文章摘要 (可选)"
></textarea>
<div class="text-art-error text-xs mt-1" v-if="errors.excerpt">
{{ errors.excerpt }}
</div>
</div>
</div>
</div>
<!-- Right Column: Meta & Settings -->
<div class="space-y-6">
<div class="admin-card p-6 space-y-6 sticky top-6">
<h3 class="text-sm font-medium text-white border-b border-white/5 pb-4 mb-4 flex items-center gap-2">
<span class="text-art-accent">⚙️</span> 发布设置
</h3>
<!-- Category Field -->
<div class="space-y-2">
<label for="category" class="block text-xs font-medium text-art-muted uppercase tracking-wider">分类</label>
<input
type="text"
id="category"
v-model="form.category"
class="admin-input"
placeholder="文章分类"
required
/>
<div class="text-art-error text-xs mt-1" v-if="errors.category">
{{ errors.category }}
</div>
</div>
<!-- Date Field -->
<div class="space-y-2">
<label for="date" class="block text-xs font-medium text-art-muted uppercase tracking-wider">发布日期</label>
<input
type="date"
id="date"
v-model="form.date"
class="admin-input"
required
/>
<div class="text-art-error text-xs mt-1" v-if="errors.date">
{{ errors.date }}
</div>
</div>
<!-- Is Published Field -->
<div class="space-y-2">
<label for="isPublished" class="block text-xs font-medium text-art-muted uppercase tracking-wider">状态</label>
<CustomSelect
v-model="form.isPublished"
:options="publishStatusOptions"
placeholder="请选择发布状态"
/>
<div class="text-art-error text-xs mt-1" v-if="errors.isPublished">
{{ errors.isPublished }}
</div>
</div>
<!-- Action Buttons (Mobile only, hidden on large screens) -->
<div class="lg:hidden pt-4 border-t border-white/5 flex flex-col gap-3">
<button type="button" class="admin-btn-primary w-full" @click="handleSubmit" :disabled="isSubmitting">
{{ isSubmitting ? '提交中...' : (isEditing ? '更新文章' : '发布文章') }}
</button>
<button type="button" class="admin-btn-secondary w-full" @click="handleCancel">取消</button>
</div>
</div>
</div>
</form>
</div>
</template>
@@ -188,18 +212,18 @@ const handleSubmit = async () => {
if (isEditing.value) {
// Update existing post
await updatePost(route.params.id as string, form)
toast.success('文章更新成功')
toast.showToast('文章更新成功', 'success')
} else {
// Create new post
await createPost(form)
toast.success('文章创建成功')
toast.showToast('文章创建成功', 'success')
}
// Redirect to posts list
router.push('/admin/posts')
} catch (error: any) {
console.error('Error submitting form:', error)
toast.error(error.message || (isEditing.value ? '更新文章失败' : '创建文章失败'))
toast.showToast(error.message || (isEditing.value ? '更新文章失败' : '创建文章失败'), 'error')
} finally {
isSubmitting.value = false
}
@@ -227,159 +251,24 @@ onMounted(async () => {
form.isPublished = post.isPublished === 1 ? 1 : 0
} catch (error: any) {
console.error('Failed to fetch post data:', error)
toast.error('加载文章数据失败: ' + (error.message || '未知错误'))
toast.showToast('加载文章数据失败: ' + (error.message || '未知错误'), 'error')
}
}
})
</script>
<style scoped>
.post-form-container {
width: 100%;
}
.page-title {
font-size: 1.75rem;
font-weight: 600;
color: #d4b383;
margin-bottom: 1.5rem;
font-family: 'Inter', sans-serif;
}
.form-container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 2rem;
}
.post-form {
max-width: 800px;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
font-size: 0.95rem;
font-weight: 500;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 0.5rem;
font-family: 'Inter', sans-serif;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 0.75rem;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 0.5rem;
font-size: 0.95rem;
background: rgba(255, 255, 255, 0.05);
color: white;
font-family: 'Inter', sans-serif;
resize: vertical;
}
.form-group input::placeholder,
.form-group textarea::placeholder,
.form-group select::placeholder {
color: rgba(255, 255, 255, 0.5);
}
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
outline: none;
border-color: #d4b383;
box-shadow: 0 0 0 3px rgba(212, 179, 131, 0.2);
}
.error-message {
color: #ef4444;
font-size: 0.875rem;
margin-top: 0.25rem;
font-family: 'Inter', sans-serif;
}
.form-actions {
display: flex;
gap: 1rem;
margin-top: 2rem;
}
.cancel-btn {
padding: 0.75rem 1.5rem;
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 0.5rem;
cursor: pointer;
font-weight: 500;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
font-family: 'Inter', sans-serif;
}
.cancel-btn:hover {
background: rgba(212, 179, 131, 0.1);
border-color: #d4b383;
color: #d4b383;
}
.submit-btn {
padding: 0.75rem 1.5rem;
background-color: #d4b383;
color: #050505;
border: 1px solid #d4b383;
border-radius: 0.5rem;
cursor: pointer;
font-weight: 500;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
font-family: 'Inter', sans-serif;
}
.submit-btn:hover:not(:disabled) {
background-color: transparent;
color: #d4b383;
transform: translateY(-1px);
box-shadow: 0 0 15px rgba(212, 179, 131, 0.3);
}
.submit-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* MdEditor Customization */
.editor-wrapper {
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 0.5rem;
overflow: hidden;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.editor-wrapper:focus-within {
border-color: #d4b383;
box-shadow: 0 0 0 3px rgba(212, 179, 131, 0.2);
}
:deep(.custom-md-editor) {
background-color: rgba(255, 255, 255, 0.05) !important;
background-color: transparent !important;
--md-bk-color: transparent !important;
--md-color: white !important;
--md-border-color: transparent !important;
height: 500px;
height: 600px;
}
:deep(.md-editor-toolbar-wrapper) {
background-color: rgba(0, 0, 0, 0.2) !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
background-color: rgba(255, 255, 255, 0.02) !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important;
}
:deep(.md-editor-content) {
@@ -387,23 +276,7 @@ onMounted(async () => {
}
:deep(.md-editor-footer) {
background-color: rgba(0, 0, 0, 0.2) !important;
border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
background-color: rgba(255, 255, 255, 0.02) !important;
border-top: 1px solid rgba(255, 255, 255, 0.05) !important;
}
/* Responsive Design */
@media (max-width: 768px) {
.form-container {
padding: 1.5rem;
}
.form-actions {
flex-direction: column;
}
.cancel-btn,
.submit-btn {
width: 100%;
}
}
</style>
</style>